1722 lines
57 KiB
C++
1722 lines
57 KiB
C++
#include "dtsc.h"
|
|
#include "defines.h"
|
|
#include "bitfields.h"
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#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 {
|
|
/// Default constructor for packets - sets a null pointer and invalid packet.
|
|
Packet::Packet() {
|
|
data = NULL;
|
|
bufferLen = 0;
|
|
dataLen = 0;
|
|
master = false;
|
|
version = DTSC_INVALID;
|
|
}
|
|
|
|
/// Copy constructor for packets, copies an existing packet with same noCopy flag as original.
|
|
Packet::Packet(const Packet & rhs) {
|
|
Packet(rhs.data, rhs.dataLen, !rhs.master);
|
|
}
|
|
|
|
/// Data constructor for packets, either references or copies a packet from raw data.
|
|
Packet::Packet(const char * data_, unsigned int len, bool noCopy) {
|
|
master = false;
|
|
bufferLen = 0;
|
|
data = NULL;
|
|
reInit(data_, len, noCopy);
|
|
}
|
|
|
|
/// This destructor clears frees the data pointer if the packet was not a reference.
|
|
Packet::~Packet() {
|
|
if (master && data) {
|
|
free(data);
|
|
}
|
|
}
|
|
|
|
/// Copier for packets, copies an existing packet with same noCopy flag as original.
|
|
/// If going from copy to noCopy, this will free the data pointer first.
|
|
void Packet::operator = (const Packet & rhs) {
|
|
if (master && !rhs.master) {
|
|
null();
|
|
}
|
|
if (rhs) {
|
|
reInit(rhs.data, rhs.dataLen, !rhs.master);
|
|
} else {
|
|
null();
|
|
}
|
|
}
|
|
|
|
/// Returns true if the packet is deemed valid, false otherwise.
|
|
/// Valid packets have a length of at least 8, known header type, and length equal to the length set in the header.
|
|
Packet::operator bool() const {
|
|
if (!data) {
|
|
DEBUG_MSG(DLVL_DONTEVEN, "No data");
|
|
return false;
|
|
}
|
|
if (dataLen < 8) {
|
|
DEBUG_MSG(DLVL_VERYHIGH, "Datalen < 8");
|
|
return false;
|
|
}
|
|
if (version == DTSC_INVALID) {
|
|
DEBUG_MSG(DLVL_VERYHIGH, "No valid version");
|
|
return false;
|
|
}
|
|
if (ntohl(((int *)data)[1]) + 8 > dataLen) {
|
|
DEBUG_MSG(DLVL_VERYHIGH, "Length mismatch");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Returns the recognized packet type.
|
|
/// This type is set by reInit and all constructors, and then just referenced from there on.
|
|
packType Packet::getVersion() const {
|
|
return version;
|
|
}
|
|
|
|
/// Resets this packet back to the same state as if it had just been freshly constructed.
|
|
/// If needed, this frees the data pointer.
|
|
void Packet::null() {
|
|
if (master && data) {
|
|
free(data);
|
|
}
|
|
master = false;
|
|
data = NULL;
|
|
bufferLen = 0;
|
|
dataLen = 0;
|
|
version = DTSC_INVALID;
|
|
}
|
|
|
|
/// Internally used resize function for when operating in copy mode and the internal buffer is too small.
|
|
/// It will only resize up, never down.
|
|
///\param len The length th scale the buffer up to if necessary
|
|
void Packet::resize(unsigned int len) {
|
|
if (master && len > bufferLen) {
|
|
char * tmp = (char *)realloc(data, len);
|
|
if (tmp) {
|
|
data = tmp;
|
|
bufferLen = len;
|
|
} else {
|
|
DEBUG_MSG(DLVL_FAIL, "Out of memory on parsing a packet");
|
|
}
|
|
}
|
|
}
|
|
|
|
///\brief Initializes a packet with new data
|
|
///\param data_ The new data for the packet
|
|
///\param len The length of the data pointed to by data_
|
|
///\param noCopy Determines whether to make a copy or not
|
|
void Packet::reInit(const char * data_, unsigned int len, bool noCopy) {
|
|
if (!data_) {
|
|
DEBUG_MSG(DLVL_DEVEL, "ReInit received a null pointer with len %d, ignoring", len);
|
|
null();
|
|
return;
|
|
}
|
|
if (!data_[0] && !data_[1] && !data_[2] && !data_[3]){
|
|
null();
|
|
return;
|
|
}
|
|
if (data_[0] != 'D' || data_[1] != 'T') {
|
|
unsigned int twlen = len;
|
|
if (twlen > 20){twlen = 20;}
|
|
DEBUG_MSG(DLVL_HIGH, "ReInit received a pointer that didn't start with 'DT' but with %s (%u) - data corruption?", JSON::Value(std::string(data_, twlen)).toString().c_str(), len);
|
|
null();
|
|
return;
|
|
}
|
|
if (len <= 0) {
|
|
len = ntohl(((int *)data_)[1]) + 8;
|
|
}
|
|
//clear any existing controlled contents
|
|
if (master && noCopy) {
|
|
null();
|
|
}
|
|
//set control flag to !noCopy
|
|
master = !noCopy;
|
|
//either copy the data, or only the pointer, depending on flag
|
|
if (noCopy) {
|
|
data = (char *)data_;
|
|
} else {
|
|
resize(len);
|
|
memcpy(data, data_, len);
|
|
}
|
|
//check header type and store packet length
|
|
dataLen = len;
|
|
version = DTSC_INVALID;
|
|
if (len > 3) {
|
|
if (!memcmp(data, Magic_Packet2, 4)) {
|
|
version = DTSC_V2;
|
|
} else {
|
|
if (!memcmp(data, Magic_Packet, 4)) {
|
|
version = DTSC_V1;
|
|
} else {
|
|
if (!memcmp(data, Magic_Header, 4)) {
|
|
version = DTSC_HEAD;
|
|
} else {
|
|
DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with invalid header");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with size < 4");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// Re-initializes this Packet to contain a generic DTSC packet with the given data fields.
|
|
void Packet::genericFill(long long packTime, long long packOffset, long long packTrack, char * packData, long long packDataSize, long long packBytePos, bool isKeyframe){
|
|
null();
|
|
master = true;
|
|
//time and trackID are part of the 20-byte header.
|
|
//the container object adds 4 bytes (plus 2+namelen for each content, see below)
|
|
//offset, if non-zero, adds 9 bytes (integer type) and 8 bytes (2+namelen)
|
|
//bpos, if >= 0, adds 9 bytes (integer type) and 6 bytes (2+namelen)
|
|
//keyframe, if true, adds 9 bytes (integer type) and 10 bytes (2+namelen)
|
|
//data adds packDataSize+5 bytes (string type) and 6 bytes (2+namelen)
|
|
unsigned int sendLen = 24 + (packOffset?17:0) + (packBytePos>=0?15:0) + (isKeyframe?19:0) + packDataSize+11;
|
|
resize(sendLen);
|
|
//set internal variables
|
|
version = DTSC_V2;
|
|
dataLen = sendLen;
|
|
//write the first 20 bytes
|
|
memcpy(data, "DTP2", 4);
|
|
unsigned int tmpLong = htonl(sendLen - 8);
|
|
memcpy(data+4, (char *)&tmpLong, 4);
|
|
tmpLong = htonl(packTrack);
|
|
memcpy(data+8, (char *)&tmpLong, 4);
|
|
tmpLong = htonl((int)(packTime >> 32));
|
|
memcpy(data+12, (char *)&tmpLong, 4);
|
|
tmpLong = htonl((int)(packTime & 0xFFFFFFFF));
|
|
memcpy(data+16, (char *)&tmpLong, 4);
|
|
data[20] = 0xE0;//start container object
|
|
unsigned int offset = 21;
|
|
if (packOffset){
|
|
memcpy(data+offset, "\000\006offset\001", 9);
|
|
tmpLong = htonl((int)(packOffset >> 32));
|
|
memcpy(data+offset+9, (char *)&tmpLong, 4);
|
|
tmpLong = htonl((int)(packOffset & 0xFFFFFFFF));
|
|
memcpy(data+offset+13, (char *)&tmpLong, 4);
|
|
offset += 17;
|
|
}
|
|
if (packBytePos){
|
|
memcpy(data+offset, "\000\004bpos\001", 7);
|
|
tmpLong = htonl((int)(packBytePos >> 32));
|
|
memcpy(data+offset+7, (char *)&tmpLong, 4);
|
|
tmpLong = htonl((int)(packBytePos & 0xFFFFFFFF));
|
|
memcpy(data+offset+11, (char *)&tmpLong, 4);
|
|
offset += 15;
|
|
}
|
|
if (isKeyframe){
|
|
memcpy(data+offset, "\000\010keyframe\001\000\000\000\000\000\000\000\001", 19);
|
|
offset += 19;
|
|
}
|
|
memcpy(data+offset, "\000\004data\002", 7);
|
|
tmpLong = htonl(packDataSize);
|
|
memcpy(data+offset+7, (char *)&tmpLong, 4);
|
|
memcpy(data+offset+11, packData, packDataSize);
|
|
//finish container with 0x0000EE
|
|
memcpy(data+offset+11+packDataSize, "\000\000\356", 3);
|
|
}
|
|
|
|
/// Helper function for skipping over whole DTSC parts
|
|
static char * skipDTSC(char * p, char * max) {
|
|
if (p + 1 >= max || p[0] == 0x00) {
|
|
return 0;//out of packet! 1 == error
|
|
}
|
|
if (p[0] == DTSC_INT) {
|
|
//int, skip 9 bytes to next value
|
|
return p + 9;
|
|
}
|
|
if (p[0] == DTSC_STR) {
|
|
if (p + 4 >= max) {
|
|
return 0;//out of packet!
|
|
}
|
|
return p + 5 + Bit::btohl(p+1);
|
|
}
|
|
if (p[0] == DTSC_OBJ || p[0] == DTSC_CON) {
|
|
p++;
|
|
//object, scan contents
|
|
while (p[0] + p[1] != 0 && p < max) { //while not encountering 0x0000 (we assume 0x0000EE)
|
|
if (p + 2 >= max) {
|
|
return 0;//out of packet!
|
|
}
|
|
p += 2 + Bit::btohs(p);//skip size
|
|
//otherwise, search through the contents, if needed, and continue
|
|
p = skipDTSC(p, max);
|
|
if (!p) {
|
|
return 0;
|
|
}
|
|
}
|
|
return p + 3;
|
|
}
|
|
if (p[0] == DTSC_ARR) {
|
|
p++;
|
|
//array, scan contents
|
|
while (p[0] + p[1] != 0 && p < max) { //while not encountering 0x0000 (we assume 0x0000EE)
|
|
//search through contents...
|
|
p = skipDTSC(p, max);
|
|
if (!p) {
|
|
return 0;
|
|
}
|
|
}
|
|
return p + 3; //skip end marker
|
|
}
|
|
return 0;//out of packet! 1 == error
|
|
}
|
|
|
|
///\brief Retrieves a single parameter as a string
|
|
///\param identifier The name of the parameter
|
|
///\param result A location on which the string will be returned
|
|
///\param len An integer in which the length of the string will be returned
|
|
void Packet::getString(const char * identifier, char *& result, unsigned int & len) const {
|
|
getScan().getMember(identifier).getString(result, len);
|
|
}
|
|
|
|
///\brief Retrieves a single parameter as a string
|
|
///\param identifier The name of the parameter
|
|
///\param result The string in which to store the result
|
|
void Packet::getString(const char * identifier, std::string & result) const {
|
|
result = getScan().getMember(identifier).asString();
|
|
}
|
|
|
|
///\brief Retrieves a single parameter as an integer
|
|
///\param identifier The name of the parameter
|
|
///\param result The result is stored in this integer
|
|
void Packet::getInt(const char * identifier, int & result) const {
|
|
result = getScan().getMember(identifier).asInt();
|
|
}
|
|
|
|
///\brief Retrieves a single parameter as an integer
|
|
///\param identifier The name of the parameter
|
|
///\result The requested parameter as an integer
|
|
int Packet::getInt(const char * identifier) const {
|
|
int result;
|
|
getInt(identifier, result);
|
|
return result;
|
|
}
|
|
|
|
///\brief Retrieves a single parameter as a boolean
|
|
///\param identifier The name of the parameter
|
|
///\param result The result is stored in this boolean
|
|
void Packet::getFlag(const char * identifier, bool & result) const {
|
|
int result_;
|
|
getInt(identifier, result_);
|
|
result = (bool)result_;
|
|
}
|
|
|
|
///\brief Retrieves a single parameter as a boolean
|
|
///\param identifier The name of the parameter
|
|
///\result The requested parameter as a boolean
|
|
bool Packet::getFlag(const char * identifier) const {
|
|
bool result;
|
|
getFlag(identifier, result);
|
|
return result;
|
|
}
|
|
|
|
///\brief Checks whether a parameter exists
|
|
///\param identifier The name of the parameter
|
|
///\result Whether the parameter exists or not
|
|
bool Packet::hasMember(const char * identifier) const {
|
|
return getScan().getMember(identifier).getType() > 0;
|
|
}
|
|
|
|
///\brief Returns the timestamp of the packet.
|
|
///\return The timestamp of this packet.
|
|
long long unsigned int Packet::getTime() const {
|
|
if (version != DTSC_V2) {
|
|
if (!data) {
|
|
return 0;
|
|
}
|
|
return getInt("time");
|
|
}
|
|
return Bit::btohll(data + 12);
|
|
}
|
|
|
|
///\brief Returns the track id of the packet.
|
|
///\return The track id of this packet.
|
|
long int Packet::getTrackId() const {
|
|
if (version != DTSC_V2) {
|
|
return getInt("trackid");
|
|
}
|
|
return Bit::btohl(data+8);
|
|
}
|
|
|
|
///\brief Returns a pointer to the payload of this packet.
|
|
///\return A pointer to the payload of this packet.
|
|
char * Packet::getData() const {
|
|
return data;
|
|
}
|
|
|
|
///\brief Returns the size of this packet.
|
|
///\return The size of this packet.
|
|
int Packet::getDataLen() const {
|
|
return dataLen;
|
|
}
|
|
|
|
///\brief Returns the size of the payload of this packet.
|
|
///\return The size of the payload of this packet.
|
|
int Packet::getPayloadLen() const {
|
|
if (version == DTSC_V2) {
|
|
return dataLen - 20;
|
|
} else {
|
|
return dataLen - 8;
|
|
}
|
|
}
|
|
|
|
/// Returns a DTSC::Scan instance to the contents of this packet.
|
|
/// May return an invalid instance if this packet is invalid.
|
|
Scan Packet::getScan() const {
|
|
if (!*this || !getDataLen() || !getPayloadLen() || getDataLen() <= getPayloadLen()){
|
|
return Scan();
|
|
}
|
|
return Scan(data + (getDataLen() - getPayloadLen()), getPayloadLen());
|
|
}
|
|
|
|
///\brief Converts the packet into a JSON value
|
|
///\return A JSON::Value representation of this packet.
|
|
JSON::Value Packet::toJSON() const {
|
|
JSON::Value result;
|
|
unsigned int i = 8;
|
|
if (getVersion() == DTSC_V1) {
|
|
JSON::fromDTMI((const unsigned char *)data, dataLen, i, result);
|
|
}
|
|
if (getVersion() == DTSC_V2) {
|
|
JSON::fromDTMI2((const unsigned char *)data, dataLen, i, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Create an invalid DTSC::Scan object by default.
|
|
Scan::Scan() {
|
|
p = 0;
|
|
len = 0;
|
|
}
|
|
|
|
|
|
/// Create a DTSC::Scan object from memory pointer.
|
|
Scan::Scan(char * pointer, size_t length) {
|
|
p = pointer;
|
|
len = length;
|
|
}
|
|
|
|
/// Returns whether the DTSC::Scan object contains valid data.
|
|
Scan::operator bool() const {
|
|
return (p && len);
|
|
}
|
|
|
|
/// 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.
|
|
Scan Scan::getMember(std::string indice) {
|
|
return getMember(indice.data(), indice.size());
|
|
}
|
|
|
|
/// 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.
|
|
Scan Scan::getMember(const char * indice, const unsigned int ind_len) {
|
|
if (getType() != DTSC_OBJ && getType() != DTSC_CON) {
|
|
return Scan();
|
|
}
|
|
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 Scan();//out of packet!
|
|
}
|
|
unsigned int strlen = Bit::btohs(i);
|
|
i += 2;
|
|
if (ind_len == strlen && strncmp(indice, i, strlen) == 0) {
|
|
return Scan(i + strlen, len - (i - p));
|
|
} else {
|
|
i = skipDTSC(i + strlen, p + len);
|
|
if (!i) {
|
|
return Scan();
|
|
}
|
|
}
|
|
}
|
|
return Scan();
|
|
}
|
|
|
|
/// 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(std::string indice){
|
|
return getMember(indice.data(), indice.size());
|
|
}
|
|
|
|
/// Returns whether an object representing the named indice of this object exists.
|
|
/// Returns false if this indice doesn't exist or this isn't an object type.
|
|
bool Scan::hasMember(const char * indice, const unsigned int ind_len) {
|
|
return getMember(indice, ind_len);
|
|
}
|
|
|
|
/// 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.
|
|
Scan Scan::getMember(const char * indice) {
|
|
return getMember(indice, strlen(indice));
|
|
}
|
|
|
|
/// Returns the amount of indices if an array, the amount of members if an object, or zero otherwise.
|
|
unsigned int Scan::getSize() {
|
|
if (getType() == DTSC_ARR) {
|
|
char * i = p + 1;
|
|
unsigned int arr_indice = 0;
|
|
//array, scan contents
|
|
while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE)
|
|
//search through contents...
|
|
arr_indice++;
|
|
i = skipDTSC(i, p + len);
|
|
if (!i) {
|
|
return arr_indice;
|
|
}
|
|
}
|
|
return arr_indice;
|
|
}
|
|
if (getType() == DTSC_OBJ || getType() == DTSC_CON) {
|
|
char * i = p + 1;
|
|
unsigned int arr_indice = 0;
|
|
//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 Scan();//out of packet!
|
|
}
|
|
unsigned int strlen = Bit::btohs(i);
|
|
i += 2;
|
|
arr_indice++;
|
|
i = skipDTSC(i + strlen, p + len);
|
|
if (!i) {
|
|
return arr_indice;
|
|
}
|
|
}
|
|
return arr_indice;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// Returns an object representing the num-th indice of this array.
|
|
/// If not an array but an object, it returns the num-th member, instead.
|
|
/// Returns an invalid object if this indice doesn't exist or this isn't an array or object type.
|
|
Scan Scan::getIndice(unsigned int num) {
|
|
if (getType() == DTSC_ARR) {
|
|
char * i = p + 1;
|
|
unsigned int arr_indice = 0;
|
|
//array, scan contents
|
|
while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE)
|
|
//search through contents...
|
|
if (arr_indice == num) {
|
|
return Scan(i, len - (i - p));
|
|
} else {
|
|
arr_indice++;
|
|
i = skipDTSC(i, p + len);
|
|
if (!i) {
|
|
return Scan();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (getType() == DTSC_OBJ || getType() == DTSC_CON) {
|
|
char * i = p + 1;
|
|
unsigned int arr_indice = 0;
|
|
//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 Scan();//out of packet!
|
|
}
|
|
unsigned int strlen = Bit::btohs(i);
|
|
i += 2;
|
|
if (arr_indice == num) {
|
|
return Scan(i + strlen, len - (i - p));
|
|
} else {
|
|
arr_indice++;
|
|
i = skipDTSC(i + strlen, p + len);
|
|
if (!i) {
|
|
return Scan();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Scan();
|
|
}
|
|
|
|
/// Returns the name of the num-th member of this object.
|
|
/// Returns an empty string on error or when not an object.
|
|
std::string Scan::getIndiceName(unsigned int num) {
|
|
if (getType() == DTSC_OBJ || getType() == DTSC_CON) {
|
|
char * i = p + 1;
|
|
unsigned int arr_indice = 0;
|
|
//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!
|
|
}
|
|
unsigned int strlen = Bit::btohs(i);
|
|
i += 2;
|
|
if (arr_indice == num) {
|
|
return std::string(i, strlen);
|
|
} else {
|
|
arr_indice++;
|
|
i = skipDTSC(i + strlen, p + len);
|
|
if (!i) {
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/// Returns the first byte of this DTSC value, or 0 on error.
|
|
char Scan::getType() {
|
|
if (!p) {
|
|
return 0;
|
|
}
|
|
return p[0];
|
|
}
|
|
|
|
/// Returns the boolean value of this DTSC value.
|
|
/// Numbers are compared to 0.
|
|
/// Strings are checked for non-zero length.
|
|
/// Objects and arrays are checked for content.
|
|
/// Returns false on error or in other cases.
|
|
bool Scan::asBool() {
|
|
switch (getType()) {
|
|
case DTSC_STR:
|
|
return (p[1] | p[2] | p[3] | p[4]);
|
|
case DTSC_INT:
|
|
return (asInt() != 0);
|
|
case DTSC_OBJ:
|
|
case DTSC_CON:
|
|
case DTSC_ARR:
|
|
return (p[1] | p[2]);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Returns the long long value of this DTSC number value.
|
|
/// Will convert string values to numbers, taking octal and hexadecimal types into account.
|
|
/// Illegal or invalid values return 0.
|
|
long long Scan::asInt() {
|
|
switch (getType()) {
|
|
case DTSC_INT:
|
|
return Bit::btohll(p+1);
|
|
case DTSC_STR:
|
|
char * str;
|
|
unsigned int strlen;
|
|
getString(str, strlen);
|
|
if (!strlen) {
|
|
return 0;
|
|
}
|
|
return strtoll(str, 0, 0);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// Returns the string value of this DTSC string value.
|
|
/// Uses getString internally, if a string.
|
|
/// Converts integer values to strings.
|
|
/// Returns an empty string on error.
|
|
std::string Scan::asString() {
|
|
switch (getType()) {
|
|
case DTSC_INT:{
|
|
std::stringstream st;
|
|
st << asInt();
|
|
return st.str();
|
|
}
|
|
break;
|
|
case DTSC_STR:{
|
|
char * str;
|
|
unsigned int strlen;
|
|
getString(str, strlen);
|
|
return std::string(str, strlen);
|
|
}
|
|
break;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/// Sets result to a pointer to the string, and strlen to the lenght of it.
|
|
/// Sets both to zero if this isn't a DTSC string value.
|
|
/// Attempts absolutely no conversion.
|
|
void Scan::getString(char *& result, unsigned int & strlen) {
|
|
switch (getType()) {
|
|
case DTSC_STR:
|
|
result = p + 5;
|
|
strlen = Bit::btohl(p+1);
|
|
return;
|
|
default:
|
|
result = 0;
|
|
strlen = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// Returns the DTSC scan object as a JSON value
|
|
/// Returns an empty object on error.
|
|
JSON::Value Scan::asJSON(){
|
|
JSON::Value result;
|
|
unsigned int i = 0;
|
|
JSON::fromDTMI((const unsigned char*)p, len, i, result);
|
|
return result;
|
|
}
|
|
|
|
/// \todo Move this function to some generic area. Duplicate from json.cpp
|
|
static inline char hex2c(char c) {
|
|
if (c < 10) {
|
|
return '0' + c;
|
|
}
|
|
if (c < 16) {
|
|
return 'A' + (c - 10);
|
|
}
|
|
return '0';
|
|
}
|
|
|
|
/// \todo Move this function to some generic area. Duplicate from json.cpp
|
|
static std::string string_escape(const std::string val) {
|
|
std::stringstream out;
|
|
out << "\"";
|
|
for (unsigned int i = 0; i < val.size(); ++i) {
|
|
switch (val.data()[i]) {
|
|
case '"':
|
|
out << "\\\"";
|
|
break;
|
|
case '\\':
|
|
out << "\\\\";
|
|
break;
|
|
case '\n':
|
|
out << "\\n";
|
|
break;
|
|
case '\b':
|
|
out << "\\b";
|
|
break;
|
|
case '\f':
|
|
out << "\\f";
|
|
break;
|
|
case '\r':
|
|
out << "\\r";
|
|
break;
|
|
case '\t':
|
|
out << "\\t";
|
|
break;
|
|
default:
|
|
if (val.data()[i] < 32 || val.data()[i] > 126) {
|
|
out << "\\u00";
|
|
out << hex2c((val.data()[i] >> 4) & 0xf);
|
|
out << hex2c(val.data()[i] & 0xf);
|
|
} else {
|
|
out << val.data()[i];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
out << "\"";
|
|
return out.str();
|
|
}
|
|
|
|
std::string Scan::toPrettyString(unsigned int indent) {
|
|
switch (getType()) {
|
|
case DTSC_STR: {
|
|
unsigned int strlen = Bit::btohl(p+1);
|
|
if (strlen > 250) {
|
|
std::stringstream ret;
|
|
ret << "\"" << strlen << " bytes of data\"";
|
|
return ret.str();
|
|
}
|
|
return string_escape(asString());
|
|
}
|
|
case DTSC_INT: {
|
|
std::stringstream ret;
|
|
ret << asInt();
|
|
return ret.str();
|
|
}
|
|
case DTSC_OBJ:
|
|
case DTSC_CON: {
|
|
std::stringstream ret;
|
|
ret << "{" << std::endl;
|
|
indent += 2;
|
|
char * i = p + 1;
|
|
bool first = true;
|
|
//object, scan contents
|
|
while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE)
|
|
if (i + 2 >= p + len) {
|
|
indent -= 2;
|
|
ret << std::string((size_t)indent, ' ') << "} //walked out of object here";
|
|
return ret.str();
|
|
}
|
|
if (!first){
|
|
ret << "," << std::endl;
|
|
}
|
|
first = false;
|
|
unsigned int strlen = Bit::btohs(i);
|
|
i += 2;
|
|
ret << std::string((size_t)indent, ' ') << "\"" << std::string(i, strlen) << "\": " << Scan(i + strlen, len - (i - p)).toPrettyString(indent);
|
|
i = skipDTSC(i + strlen, p + len);
|
|
if (!i) {
|
|
indent -= 2;
|
|
ret << std::string((size_t)indent, ' ') << "} //could not locate next object";
|
|
return ret.str();
|
|
}
|
|
}
|
|
indent -= 2;
|
|
ret << std::endl << std::string((size_t)indent, ' ') << "}";
|
|
return ret.str();
|
|
}
|
|
case DTSC_ARR: {
|
|
std::stringstream ret;
|
|
ret << "[" << std::endl;
|
|
indent += 2;
|
|
Scan tmpScan;
|
|
unsigned int i = 0;
|
|
bool first = true;
|
|
do {
|
|
tmpScan = getIndice(i++);
|
|
if (tmpScan.getType()) {
|
|
if (!first){
|
|
ret << "," << std::endl;
|
|
}
|
|
first = false;
|
|
ret << std::string((size_t)indent, ' ') << tmpScan.toPrettyString(indent);
|
|
}
|
|
} while (tmpScan.getType());
|
|
indent -= 2;
|
|
ret << std::endl << std::string((size_t)indent, ' ') << "]";
|
|
return ret.str();
|
|
}
|
|
default:
|
|
return "Error";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///\brief Returns the payloadsize of a part
|
|
long Part::getSize() {
|
|
return Bit::btoh24(data);
|
|
}
|
|
|
|
///\brief Sets the payloadsize of a part
|
|
void Part::setSize(long newSize) {
|
|
Bit::htob24(data, newSize);
|
|
}
|
|
|
|
///\brief Retruns the duration of a part
|
|
short Part::getDuration() {
|
|
return Bit::btohs(data + 3);
|
|
}
|
|
|
|
///\brief Sets the duration of a part
|
|
void Part::setDuration(short newDuration) {
|
|
Bit::htobs(data + 3, newDuration);
|
|
}
|
|
|
|
///\brief returns the offset of a part
|
|
long Part::getOffset() {
|
|
return Bit::btohl(data + 5);
|
|
}
|
|
|
|
///\brief Sets the offset of a part
|
|
void Part::setOffset(long newOffset) {
|
|
Bit::htobl(data + 5, newOffset);
|
|
}
|
|
|
|
///\brief Returns the data of a part
|
|
char * Part::getData() {
|
|
return data;
|
|
}
|
|
|
|
///\brief Converts a part to a human readable string
|
|
///\param str The stringstream to append to
|
|
///\param indent the amount of indentation needed
|
|
void Part::toPrettyString(std::ostream & str, int indent) {
|
|
str << std::string(indent, ' ') << "Part: Size(" << getSize() << "), Dur(" << getDuration() << "), Offset(" << getOffset() << ")" << std::endl;
|
|
}
|
|
|
|
///\brief Returns the byteposition of a keyframe
|
|
unsigned long long Key::getBpos() {
|
|
return (((unsigned long long)data[0] << 32) | (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4]);
|
|
}
|
|
|
|
void Key::setBpos(unsigned long long newBpos) {
|
|
data[4] = newBpos & 0xFF;
|
|
data[3] = (newBpos >> 8) & 0xFF;
|
|
data[2] = (newBpos >> 16) & 0xFF;
|
|
data[1] = (newBpos >> 24) & 0xFF;
|
|
data[0] = (newBpos >> 32) & 0xFF;
|
|
}
|
|
|
|
unsigned long Key::getLength() {
|
|
return Bit::btoh24(data+5);
|
|
}
|
|
|
|
void Key::setLength(unsigned long newLength) {
|
|
Bit::htob24(data+5, newLength);
|
|
}
|
|
|
|
///\brief Returns the number of a keyframe
|
|
unsigned long Key::getNumber() {
|
|
return Bit::btohs(data + 8);
|
|
}
|
|
|
|
///\brief Sets the number of a keyframe
|
|
void Key::setNumber(unsigned long newNumber) {
|
|
Bit::htobs(data + 8, newNumber);
|
|
}
|
|
|
|
///\brief Returns the number of parts of a keyframe
|
|
unsigned short Key::getParts() {
|
|
return Bit::btohs(data + 10);
|
|
}
|
|
|
|
///\brief Sets the number of parts of a keyframe
|
|
void Key::setParts(unsigned short newParts) {
|
|
Bit::htobs(data + 10, newParts);
|
|
}
|
|
|
|
///\brief Returns the timestamp of a keyframe
|
|
unsigned long long Key::getTime() {
|
|
return Bit::btohl(data + 12);
|
|
}
|
|
|
|
///\brief Sets the timestamp of a keyframe
|
|
void Key::setTime(unsigned long long newTime) {
|
|
Bit::htobl(data + 12, newTime);
|
|
}
|
|
|
|
///\brief Returns the data of this keyframe struct
|
|
char * Key::getData() {
|
|
return data;
|
|
}
|
|
|
|
///\brief Converts a keyframe to a human readable string
|
|
///\param str The stringstream to append to
|
|
///\param indent the amount of indentation needed
|
|
void Key::toPrettyString(std::ostream & str, int indent) {
|
|
str << std::string(indent, ' ') << "Key " << getNumber() << ": Pos(" << getBpos() << "), Dur(" << getLength() << "), Parts(" << getParts() << "), Time(" << getTime() << ")" << std::endl;
|
|
}
|
|
|
|
///\brief Returns the duration of this fragment
|
|
unsigned long Fragment::getDuration() {
|
|
return Bit::btohl(data);
|
|
}
|
|
|
|
///\brief Sets the duration of this fragment
|
|
void Fragment::setDuration(unsigned long newDuration) {
|
|
Bit::htobl(data, newDuration);
|
|
}
|
|
|
|
///\brief Returns the length of this fragment
|
|
char Fragment::getLength() {
|
|
return data[4];
|
|
}
|
|
|
|
///\brief Sets the length of this fragment
|
|
void Fragment::setLength(char newLength) {
|
|
data[4] = newLength;
|
|
}
|
|
|
|
///\brief Returns the number of the first keyframe in this fragment
|
|
unsigned long Fragment::getNumber() {
|
|
return Bit::btohs(data + 5);
|
|
}
|
|
|
|
///\brief Sets the number of the first keyframe in this fragment
|
|
void Fragment::setNumber(unsigned long newNumber) {
|
|
Bit::htobs(data + 5, newNumber);
|
|
}
|
|
|
|
///\brief Returns the size of a fragment
|
|
unsigned long Fragment::getSize() {
|
|
return Bit::btohl(data + 7);
|
|
}
|
|
|
|
///\brief Sets the size of a fragement
|
|
void Fragment::setSize(unsigned long newSize) {
|
|
Bit::htobl(data + 7, newSize);
|
|
}
|
|
|
|
///\brief Returns thte data of this fragment structure
|
|
char * Fragment::getData() {
|
|
return data;
|
|
}
|
|
|
|
///\brief Converts a fragment to a human readable string
|
|
///\param str The stringstream to append to
|
|
///\param indent the amount of indentation needed
|
|
void Fragment::toPrettyString(std::ostream & str, int indent) {
|
|
str << std::string(indent, ' ') << "Fragment " << getNumber() << ": Dur(" << getDuration() << "), Len(" << (int)getLength() << "), Size(" << getSize() << ")" << std::endl;
|
|
}
|
|
|
|
///\brief Constructs an empty track
|
|
Track::Track() {
|
|
trackID = 0;
|
|
firstms = 0;
|
|
lastms = 0;
|
|
bps = 0;
|
|
missedFrags = 0;
|
|
rate = 0;
|
|
size = 0;
|
|
channels = 0;
|
|
width = 0;
|
|
height = 0;
|
|
fpks = 0;
|
|
}
|
|
|
|
///\brief Constructs a track from a JSON::Value
|
|
Track::Track(JSON::Value & trackRef) {
|
|
if (trackRef.isMember("fragments") && trackRef["fragments"].isString()) {
|
|
Fragment * tmp = (Fragment *)trackRef["fragments"].asStringRef().data();
|
|
fragments = std::deque<Fragment>(tmp, tmp + (trackRef["fragments"].asStringRef().size() / 11));
|
|
}
|
|
if (trackRef.isMember("keys") && trackRef["keys"].isString()) {
|
|
Key * tmp = (Key *)trackRef["keys"].asStringRef().data();
|
|
keys = std::deque<Key>(tmp, tmp + (trackRef["keys"].asStringRef().size() / 16));
|
|
}
|
|
if (trackRef.isMember("parts") && trackRef["parts"].isString()) {
|
|
Part * tmp = (Part *)trackRef["parts"].asStringRef().data();
|
|
parts = std::deque<Part>(tmp, tmp + (trackRef["parts"].asStringRef().size() / 9));
|
|
}
|
|
trackID = trackRef["trackid"].asInt();
|
|
firstms = trackRef["firstms"].asInt();
|
|
lastms = trackRef["lastms"].asInt();
|
|
bps = trackRef["bps"].asInt();
|
|
missedFrags = trackRef["missed_frags"].asInt();
|
|
codec = trackRef["codec"].asStringRef();
|
|
type = trackRef["type"].asStringRef();
|
|
init = trackRef["init"].asStringRef();
|
|
if (type == "audio") {
|
|
rate = trackRef["rate"].asInt();
|
|
size = trackRef["size"].asInt();
|
|
channels = trackRef["channels"].asInt();
|
|
}
|
|
if (type == "video") {
|
|
width = trackRef["width"].asInt();
|
|
height = trackRef["height"].asInt();
|
|
fpks = trackRef["fpks"].asInt();
|
|
}
|
|
if (trackRef.isMember("keysizes") && trackRef["keysizes"].isString()) {
|
|
std::string tmp = trackRef["keysizes"].asStringRef();
|
|
for (unsigned int i = 0; i < tmp.size(); i += 4){
|
|
keySizes.push_back((((long unsigned)tmp[i]) << 24) | (((long unsigned)tmp[i+1]) << 16) | (((long unsigned int)tmp[i+2]) << 8) | tmp[i+3]);
|
|
}
|
|
}
|
|
}
|
|
|
|
///\brief Constructs a track from a JSON::Value
|
|
Track::Track(Scan & trackRef) {
|
|
if (trackRef.getMember("fragments").getType() == DTSC_STR) {
|
|
char * tmp = 0;
|
|
unsigned int tmplen = 0;
|
|
trackRef.getMember("fragments").getString(tmp, tmplen);
|
|
fragments = std::deque<Fragment>((Fragment *)tmp, ((Fragment *)tmp) + (tmplen / 11));
|
|
}
|
|
if (trackRef.getMember("keys").getType() == DTSC_STR) {
|
|
char * tmp = 0;
|
|
unsigned int tmplen = 0;
|
|
trackRef.getMember("keys").getString(tmp, tmplen);
|
|
keys = std::deque<Key>((Key *)tmp, ((Key *)tmp) + (tmplen / 16));
|
|
}
|
|
if (trackRef.getMember("parts").getType() == DTSC_STR) {
|
|
char * tmp = 0;
|
|
unsigned int tmplen = 0;
|
|
trackRef.getMember("parts").getString(tmp, tmplen);
|
|
parts = std::deque<Part>((Part *)tmp, ((Part *)tmp) + (tmplen / 9));
|
|
}
|
|
trackID = trackRef.getMember("trackid").asInt();
|
|
firstms = trackRef.getMember("firstms").asInt();
|
|
lastms = trackRef.getMember("lastms").asInt();
|
|
bps = trackRef.getMember("bps").asInt();
|
|
missedFrags = trackRef.getMember("missed_frags").asInt();
|
|
codec = trackRef.getMember("codec").asString();
|
|
type = trackRef.getMember("type").asString();
|
|
init = trackRef.getMember("init").asString();
|
|
if (type == "audio") {
|
|
rate = trackRef.getMember("rate").asInt();
|
|
size = trackRef.getMember("size").asInt();
|
|
channels = trackRef.getMember("channels").asInt();
|
|
}
|
|
if (type == "video") {
|
|
width = trackRef.getMember("width").asInt();
|
|
height = trackRef.getMember("height").asInt();
|
|
fpks = trackRef.getMember("fpks").asInt();
|
|
}
|
|
if (trackRef.getMember("keysizes").getType() == DTSC_STR) {
|
|
char * tmp = 0;
|
|
unsigned int tmplen = 0;
|
|
trackRef.getMember("keysizes").getString(tmp, tmplen);
|
|
for (unsigned int i = 0; i < tmplen; i += 4){
|
|
keySizes.push_back((((long unsigned)tmp[i]) << 24) | (((long unsigned)tmp[i+1]) << 16) | (((long unsigned int)tmp[i+2]) << 8) | tmp[i+3]);
|
|
}
|
|
}
|
|
}
|
|
|
|
///\brief Updates a track and its metadata given new packet properties.
|
|
///Will also insert keyframes on non-video tracks, and creates fragments
|
|
void Track::update(long long packTime, long long packOffset, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size) {
|
|
if ((unsigned long long)packTime < lastms) {
|
|
DEBUG_MSG(DLVL_WARN, "Received packets for track %u in wrong order (%lld < %llu) - ignoring!", trackID, packTime, lastms);
|
|
return;
|
|
}
|
|
Part newPart;
|
|
newPart.setSize(packDataSize);
|
|
newPart.setOffset(packOffset);
|
|
if (parts.size()) {
|
|
parts[parts.size() - 1].setDuration(packTime - lastms);
|
|
newPart.setDuration(packTime - lastms);
|
|
} else {
|
|
newPart.setDuration(0);
|
|
}
|
|
parts.push_back(newPart);
|
|
lastms = packTime;
|
|
if (isKeyframe || !keys.size() || (type != "video" && packTime >= AUDIO_KEY_INTERVAL && packTime - (unsigned long long)keys[keys.size() - 1].getTime() >= AUDIO_KEY_INTERVAL)){
|
|
Key newKey;
|
|
newKey.setTime(packTime);
|
|
newKey.setParts(0);
|
|
newKey.setLength(0);
|
|
if (keys.size()) {
|
|
newKey.setNumber(keys[keys.size() - 1].getNumber() + 1);
|
|
keys[keys.size() - 1].setLength(packTime - keys[keys.size() - 1].getTime());
|
|
} else {
|
|
newKey.setNumber(1);
|
|
}
|
|
if (packBytePos >= 0) { //For VoD
|
|
newKey.setBpos(packBytePos);
|
|
} else {
|
|
newKey.setBpos(0);
|
|
}
|
|
keys.push_back(newKey);
|
|
keySizes.push_back(0);
|
|
firstms = keys[0].getTime();
|
|
if (!fragments.size() || ((unsigned long long)packTime > segment_size && (unsigned long long)packTime - segment_size >= (unsigned long long)getKey(fragments.rbegin()->getNumber()).getTime())) {
|
|
//new fragment
|
|
Fragment newFrag;
|
|
newFrag.setDuration(0);
|
|
newFrag.setLength(1);
|
|
newFrag.setNumber(keys[keys.size() - 1].getNumber());
|
|
if (fragments.size()) {
|
|
fragments[fragments.size() - 1].setDuration(packTime - getKey(fragments[fragments.size() - 1].getNumber()).getTime());
|
|
if (!bps && fragments[fragments.size() - 1].getDuration() > 1000) {
|
|
bps = (fragments[fragments.size() - 1].getSize() * 1000) / fragments[fragments.size() - 1].getDuration();
|
|
}
|
|
}
|
|
newFrag.setDuration(0);
|
|
newFrag.setSize(0);
|
|
fragments.push_back(newFrag);
|
|
} else {
|
|
Fragment & lastFrag = fragments[fragments.size() - 1];
|
|
lastFrag.setLength(lastFrag.getLength() + 1);
|
|
}
|
|
}
|
|
keys.rbegin()->setParts(keys.rbegin()->getParts() + 1);
|
|
(*keySizes.rbegin()) += packSendSize;
|
|
fragments.rbegin()->setSize(fragments.rbegin()->getSize() + packDataSize);
|
|
}
|
|
|
|
///\brief Returns a key given its number, or an empty key if the number is out of bounds
|
|
Key & Track::getKey(unsigned int keyNum) {
|
|
static Key empty;
|
|
if (keyNum < keys[0].getNumber()) {
|
|
return empty;
|
|
}
|
|
if ((keyNum - keys[0].getNumber()) > keys.size()) {
|
|
return empty;
|
|
}
|
|
return keys[keyNum - keys[0].getNumber()];
|
|
}
|
|
|
|
unsigned int Track::timeToKeynum(unsigned int timestamp){
|
|
unsigned int result = 0;
|
|
for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++){
|
|
if (it->getTime() >= timestamp){
|
|
break;
|
|
}
|
|
result = it->getNumber();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
unsigned int Track::timeToFragnum(unsigned int timestamp){
|
|
unsigned long long int totalTime = firstms;
|
|
for (unsigned int i = 0; i<fragments.size(); i++){
|
|
if (timestamp <= totalTime){
|
|
return i;
|
|
}
|
|
totalTime += fragments[i].getDuration();
|
|
}
|
|
return fragments.size()-1;
|
|
}
|
|
|
|
///\brief Resets a track, clears all meta values
|
|
void Track::reset() {
|
|
fragments.clear();
|
|
parts.clear();
|
|
keySizes.clear();
|
|
keys.clear();
|
|
bps = 0;
|
|
firstms = 0;
|
|
lastms = 0;
|
|
}
|
|
|
|
///\brief Creates an empty meta object
|
|
Meta::Meta() {
|
|
vod = false;
|
|
live = false;
|
|
moreheader = 0;
|
|
merged = false;
|
|
bufferWindow = 0;
|
|
}
|
|
|
|
Meta::Meta(const DTSC::Packet & source) {
|
|
reinit(source);
|
|
}
|
|
|
|
void Meta::reinit(const DTSC::Packet & source) {
|
|
tracks.clear();
|
|
vod = source.getFlag("vod");
|
|
live = source.getFlag("live");
|
|
merged = source.getFlag("merged");
|
|
bufferWindow = source.getInt("buffer_window");
|
|
moreheader = source.getInt("moreheader");
|
|
Scan tmpTracks = source.getScan().getMember("tracks");
|
|
unsigned int num = 0;
|
|
Scan tmpTrack;
|
|
do {
|
|
tmpTrack = tmpTracks.getIndice(num);
|
|
if (tmpTrack.asBool()) {
|
|
unsigned int trackId = tmpTrack.getMember("trackid").asInt();
|
|
if (trackId) {
|
|
tracks[trackId] = Track(tmpTrack);
|
|
}
|
|
num++;
|
|
}
|
|
} while (tmpTrack.asBool());
|
|
}
|
|
|
|
///\brief Creates a meta object from a JSON::Value
|
|
Meta::Meta(JSON::Value & meta) {
|
|
vod = meta.isMember("vod") && meta["vod"];
|
|
live = meta.isMember("live") && meta["live"];
|
|
merged = meta.isMember("merged") && meta["merged"];
|
|
bufferWindow = 0;
|
|
if (meta.isMember("buffer_window")) {
|
|
bufferWindow = meta["buffer_window"].asInt();
|
|
}
|
|
for (JSON::ObjIter it = meta["tracks"].ObjBegin(); it != meta["tracks"].ObjEnd(); it++) {
|
|
if (it->second["trackid"].asInt()) {
|
|
tracks[it->second["trackid"].asInt()] = Track(it->second);
|
|
}
|
|
}
|
|
if (meta.isMember("moreheader")) {
|
|
moreheader = meta["moreheader"].asInt();
|
|
} else {
|
|
moreheader = 0;
|
|
}
|
|
}
|
|
|
|
///\brief Updates a meta object given a JSON::Value
|
|
void Meta::update(JSON::Value & pack, unsigned long segment_size) {
|
|
update(pack["time"].asInt(), pack.isMember("offset")?pack["offset"].asInt():0, pack["trackid"].asInt(), pack["data"].asStringRef().size(), pack.isMember("bpos")?pack["bpos"].asInt():-1, pack.isMember("keyframe"), pack.packedSize(), segment_size);
|
|
}
|
|
|
|
///\brief Updates a meta object given a DTSC::Packet
|
|
void Meta::update(DTSC::Packet & pack, unsigned long segment_size) {
|
|
char * data;
|
|
unsigned int dataLen;
|
|
pack.getString("data", data, dataLen);
|
|
update(pack.getTime(), pack.hasMember("offset")?pack.getInt("offset"):0, pack.getTrackId(), dataLen, pack.hasMember("bpos")?pack.getInt("bpos"):-1, pack.hasMember("keyframe"), pack.getDataLen(), segment_size);
|
|
}
|
|
|
|
///\brief Updates a meta object given a DTSC::Packet with byte position override.
|
|
void Meta::updatePosOverride(DTSC::Packet & pack, unsigned long bpos) {
|
|
char * data;
|
|
unsigned int dataLen;
|
|
pack.getString("data", data, dataLen);
|
|
update(pack.getTime(), pack.hasMember("offset")?pack.getInt("offset"):0, pack.getTrackId(), dataLen, bpos, pack.hasMember("keyframe"), pack.getDataLen());
|
|
}
|
|
|
|
void Meta::update(long long packTime, long long packOffset, long long packTrack, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size){
|
|
if (!packSendSize){
|
|
//time and trackID are part of the 20-byte header.
|
|
//the container object adds 4 bytes (plus 2+namelen for each content, see below)
|
|
//offset, if non-zero, adds 9 bytes (integer type) and 8 bytes (2+namelen)
|
|
//bpos, if >= 0, adds 9 bytes (integer type) and 6 bytes (2+namelen)
|
|
//keyframe, if true, adds 9 bytes (integer type) and 10 bytes (2+namelen)
|
|
//data adds packDataSize+5 bytes (string type) and 6 bytes (2+namelen)
|
|
packSendSize = 24 + (packOffset?17:0) + (packBytePos>=0?15:0) + (isKeyframe?19:0) + packDataSize+11;
|
|
}
|
|
vod = (packBytePos >= 0);
|
|
live = !vod;
|
|
if (packTrack > 0 && tracks.count(packTrack)){
|
|
tracks[packTrack].update(packTime, packOffset, packDataSize, packBytePos, isKeyframe, packSendSize, segment_size);
|
|
}
|
|
}
|
|
|
|
///\brief Converts a track to a human readable string
|
|
///\param str The stringstream to append to
|
|
///\param indent the amount of indentation needed
|
|
///\param verbosity How verbose the output needs to be
|
|
void Track::toPrettyString(std::ostream & str, int indent, int verbosity) {
|
|
str << std::string(indent, ' ') << "Track " << getWritableIdentifier() << std::endl;
|
|
str << std::string(indent + 2, ' ') << "ID: " << trackID << std::endl;
|
|
str << std::string(indent + 2, ' ') << "Firstms: " << firstms << std::endl;
|
|
str << std::string(indent + 2, ' ') << "Lastms: " << lastms << std::endl;
|
|
str << std::string(indent + 2, ' ') << "Bps: " << bps << std::endl;
|
|
if (missedFrags) {
|
|
str << std::string(indent + 2, ' ') << "missedFrags: " << missedFrags << std::endl;
|
|
}
|
|
str << std::string(indent + 2, ' ') << "Codec: " << codec << std::endl;
|
|
str << std::string(indent + 2, ' ') << "Type: " << type << std::endl;
|
|
str << std::string(indent + 2, ' ') << "Init: ";
|
|
for (unsigned int i = 0; i < init.size(); ++i) {
|
|
str << std::hex << std::setw(2) << std::setfill('0') << (int)init[i];
|
|
}
|
|
str << std::dec << std::endl;
|
|
if (type == "audio") {
|
|
str << std::string(indent + 2, ' ') << "Rate: " << rate << std::endl;
|
|
str << std::string(indent + 2, ' ') << "Size: " << size << std::endl;
|
|
str << std::string(indent + 2, ' ') << "Channel: " << channels << std::endl;
|
|
} else if (type == "video") {
|
|
str << std::string(indent + 2, ' ') << "Width: " << width << std::endl;
|
|
str << std::string(indent + 2, ' ') << "Height: " << height << std::endl;
|
|
str << std::string(indent + 2, ' ') << "Fpks: " << fpks << std::endl;
|
|
}
|
|
str << std::string(indent + 2, ' ') << "Fragments: " << fragments.size() << std::endl;
|
|
if (verbosity & 0x01) {
|
|
for (unsigned int i = 0; i < fragments.size(); i++) {
|
|
fragments[i].toPrettyString(str, indent + 4);
|
|
}
|
|
}
|
|
str << std::string(indent + 2, ' ') << "Keys: " << keys.size() << std::endl;
|
|
if (verbosity & 0x02) {
|
|
for (unsigned int i = 0; i < keys.size(); i++) {
|
|
keys[i].toPrettyString(str, indent + 4);
|
|
}
|
|
}
|
|
str << std::string(indent + 2, ' ') << "KeySizes: " << keySizes.size() << std::endl;
|
|
if (keySizes.size() && verbosity & 0x02){
|
|
for (unsigned int i = 0; i < keySizes.size(); i++){
|
|
str << std::string(indent + 4, ' ') << "[" << i << "] " << keySizes[i] << std::endl;
|
|
}
|
|
}
|
|
str << std::string(indent + 2, ' ') << "Parts: " << parts.size() << std::endl;
|
|
if (verbosity & 0x04) {
|
|
for (unsigned int i = 0; i < parts.size(); i++) {
|
|
parts[i].toPrettyString(str, indent + 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
///\brief Converts a short to a char*
|
|
char * convertShort(short input) {
|
|
static char result[2];
|
|
result[0] = (input >> 8) & 0xFF;
|
|
result[1] = (input) & 0xFF;
|
|
return result;
|
|
}
|
|
|
|
///\brief Converts an integer to a char*
|
|
char * convertInt(int input) {
|
|
static char result[4];
|
|
result[0] = (input >> 24) & 0xFF;
|
|
result[1] = (input >> 16) & 0xFF;
|
|
result[2] = (input >> 8) & 0xFF;
|
|
result[3] = (input) & 0xFF;
|
|
return result;
|
|
}
|
|
|
|
///\brief Converts a long long to a char*
|
|
char * convertLongLong(long long int input) {
|
|
static char result[8];
|
|
result[0] = (input >> 56) & 0xFF;
|
|
result[1] = (input >> 48) & 0xFF;
|
|
result[2] = (input >> 40) & 0xFF;
|
|
result[3] = (input >> 32) & 0xFF;
|
|
result[4] = (input >> 24) & 0xFF;
|
|
result[5] = (input >> 16) & 0xFF;
|
|
result[6] = (input >> 8) & 0xFF;
|
|
result[7] = (input) & 0xFF;
|
|
return result;
|
|
}
|
|
|
|
|
|
///\brief Returns a unique identifier for a track
|
|
std::string Track::getIdentifier() {
|
|
std::stringstream result;
|
|
if (type == "") {
|
|
result << "metadata_" << trackID;
|
|
return result.str();
|
|
}
|
|
result << type << "_";
|
|
result << codec << "_";
|
|
if (type == "audio") {
|
|
result << channels << "ch_";
|
|
result << rate << "hz";
|
|
} else if (type == "video") {
|
|
result << width << "x" << height << "_";
|
|
result << (double)fpks / 1000 << "fps";
|
|
}
|
|
return result.str();
|
|
}
|
|
|
|
///\brief Returns a writable identifier for a track, to prevent overwrites on readout
|
|
std::string Track::getWritableIdentifier() {
|
|
std::stringstream result;
|
|
result << getIdentifier() << "_" << trackID;
|
|
return result.str();
|
|
}
|
|
|
|
///\brief Determines the "packed" size of a track
|
|
int Track::getSendLen() {
|
|
int result = 146 + init.size() + codec.size() + type.size() + getWritableIdentifier().size();
|
|
result += fragments.size() * 11;
|
|
result += keys.size() * 16;
|
|
if (keySizes.size()){
|
|
result += 11 + (keySizes.size() * 4) + 4;
|
|
}
|
|
result += parts.size() * 9;
|
|
if (type == "audio") {
|
|
result += 49;
|
|
} else if (type == "video") {
|
|
result += 48;
|
|
}
|
|
if (missedFrags) {
|
|
result += 23;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///\brief Writes a pointer to the specified destination
|
|
///
|
|
///Does a memcpy and increases the destination pointer accordingly
|
|
static void writePointer(char *& p, const char * src, unsigned int len) {
|
|
memcpy(p, src, len);
|
|
p += len;
|
|
}
|
|
|
|
///\brief Writes a pointer to the specified destination
|
|
///
|
|
///Does a memcpy and increases the destination pointer accordingly
|
|
static void writePointer(char *& p, const std::string & src) {
|
|
writePointer(p, src.data(), src.size());
|
|
}
|
|
|
|
///\brief Writes a track to a pointer
|
|
void Track::writeTo(char *& p) {
|
|
writePointer(p, convertShort(getWritableIdentifier().size()), 2);
|
|
writePointer(p, getWritableIdentifier());
|
|
writePointer(p, "\340", 1);//Begin track object
|
|
writePointer(p, "\000\011fragments\002", 12);
|
|
writePointer(p, convertInt(fragments.size() * 11), 4);
|
|
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
|
|
writePointer(p, it->getData(), 11);
|
|
}
|
|
writePointer(p, "\000\004keys\002", 7);
|
|
writePointer(p, convertInt(keys.size() * 16), 4);
|
|
for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++) {
|
|
writePointer(p, it->getData(), 16);
|
|
}
|
|
writePointer(p, "\000\010keysizes\002,", 11);
|
|
writePointer(p, convertInt(keySizes.size() * 4), 4);
|
|
std::string tmp;
|
|
tmp.reserve(keySizes.size() * 4);
|
|
for (unsigned int i = 0; i < keySizes.size(); i++){
|
|
tmp += (char)(keySizes[i] >> 24);
|
|
tmp += (char)(keySizes[i] >> 16);
|
|
tmp += (char)(keySizes[i] >> 8);
|
|
tmp += (char)(keySizes[i]);
|
|
}
|
|
writePointer(p, tmp.data(), tmp.size());
|
|
writePointer(p, "\000\005parts\002", 8);
|
|
writePointer(p, convertInt(parts.size() * 9), 4);
|
|
for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) {
|
|
writePointer(p, it->getData(), 9);
|
|
}
|
|
writePointer(p, "\000\007trackid\001", 10);
|
|
writePointer(p, convertLongLong(trackID), 8);
|
|
if (missedFrags) {
|
|
writePointer(p, "\000\014missed_frags\001", 15);
|
|
writePointer(p, convertLongLong(missedFrags), 8);
|
|
}
|
|
writePointer(p, "\000\007firstms\001", 10);
|
|
writePointer(p, convertLongLong(firstms), 8);
|
|
writePointer(p, "\000\006lastms\001", 9);
|
|
writePointer(p, convertLongLong(lastms), 8);
|
|
writePointer(p, "\000\003bps\001", 6);
|
|
writePointer(p, convertLongLong(bps), 8);
|
|
writePointer(p, "\000\004init\002", 7);
|
|
writePointer(p, convertInt(init.size()), 4);
|
|
writePointer(p, init);
|
|
writePointer(p, "\000\005codec\002", 8);
|
|
writePointer(p, convertInt(codec.size()), 4);
|
|
writePointer(p, codec);
|
|
writePointer(p, "\000\004type\002", 7);
|
|
writePointer(p, convertInt(type.size()), 4);
|
|
writePointer(p, type);
|
|
if (type == "audio") {
|
|
writePointer(p, "\000\004rate\001", 7);
|
|
writePointer(p, convertLongLong(rate), 8);
|
|
writePointer(p, "\000\004size\001", 7);
|
|
writePointer(p, convertLongLong(size), 8);
|
|
writePointer(p, "\000\010channels\001", 11);
|
|
writePointer(p, convertLongLong(channels), 8);
|
|
} else if (type == "video") {
|
|
writePointer(p, "\000\005width\001", 8);
|
|
writePointer(p, convertLongLong(width), 8);
|
|
writePointer(p, "\000\006height\001", 9);
|
|
writePointer(p, convertLongLong(height), 8);
|
|
writePointer(p, "\000\004fpks\001", 7);
|
|
writePointer(p, convertLongLong(fpks), 8);
|
|
}
|
|
writePointer(p, "\000\000\356", 3);//End this track Object
|
|
}
|
|
|
|
///\brief Writes a track to a socket
|
|
void Track::send(Socket::Connection & conn) {
|
|
conn.SendNow(convertShort(getWritableIdentifier().size()), 2);
|
|
conn.SendNow(getWritableIdentifier());
|
|
conn.SendNow("\340", 1);//Begin track object
|
|
conn.SendNow("\000\011fragments\002", 12);
|
|
conn.SendNow(convertInt(fragments.size() * 11), 4);
|
|
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
|
|
conn.SendNow(it->getData(), 11);
|
|
}
|
|
conn.SendNow("\000\004keys\002", 7);
|
|
conn.SendNow(convertInt(keys.size() * 16), 4);
|
|
for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++) {
|
|
conn.SendNow(it->getData(), 16);
|
|
}
|
|
conn.SendNow("\000\010keysizes\002,", 11);
|
|
conn.SendNow(convertInt(keySizes.size() * 4), 4);
|
|
std::string tmp;
|
|
tmp.reserve(keySizes.size() * 4);
|
|
for (unsigned int i = 0; i < keySizes.size(); i++){
|
|
tmp += (char)(keySizes[i] >> 24);
|
|
tmp += (char)(keySizes[i] >> 16);
|
|
tmp += (char)(keySizes[i] >> 8);
|
|
tmp += (char)(keySizes[i]);
|
|
}
|
|
conn.SendNow(tmp.data(), tmp.size());
|
|
conn.SendNow("\000\005parts\002", 8);
|
|
conn.SendNow(convertInt(parts.size() * 9), 4);
|
|
for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) {
|
|
conn.SendNow(it->getData(), 9);
|
|
}
|
|
conn.SendNow("\000\007trackid\001", 10);
|
|
conn.SendNow(convertLongLong(trackID), 8);
|
|
if (missedFrags) {
|
|
conn.SendNow("\000\014missed_frags\001", 15);
|
|
conn.SendNow(convertLongLong(missedFrags), 8);
|
|
}
|
|
conn.SendNow("\000\007firstms\001", 10);
|
|
conn.SendNow(convertLongLong(firstms), 8);
|
|
conn.SendNow("\000\006lastms\001", 9);
|
|
conn.SendNow(convertLongLong(lastms), 8);
|
|
conn.SendNow("\000\003bps\001", 6);
|
|
conn.SendNow(convertLongLong(bps), 8);
|
|
conn.SendNow("\000\004init\002", 7);
|
|
conn.SendNow(convertInt(init.size()), 4);
|
|
conn.SendNow(init);
|
|
conn.SendNow("\000\005codec\002", 8);
|
|
conn.SendNow(convertInt(codec.size()), 4);
|
|
conn.SendNow(codec);
|
|
conn.SendNow("\000\004type\002", 7);
|
|
conn.SendNow(convertInt(type.size()), 4);
|
|
conn.SendNow(type);
|
|
if (type == "audio") {
|
|
conn.SendNow("\000\004rate\001", 7);
|
|
conn.SendNow(convertLongLong(rate), 8);
|
|
conn.SendNow("\000\004size\001", 7);
|
|
conn.SendNow(convertLongLong(size), 8);
|
|
conn.SendNow("\000\010channels\001", 11);
|
|
conn.SendNow(convertLongLong(channels), 8);
|
|
} else if (type == "video") {
|
|
conn.SendNow("\000\005width\001", 8);
|
|
conn.SendNow(convertLongLong(width), 8);
|
|
conn.SendNow("\000\006height\001", 9);
|
|
conn.SendNow(convertLongLong(height), 8);
|
|
conn.SendNow("\000\004fpks\001", 7);
|
|
conn.SendNow(convertLongLong(fpks), 8);
|
|
}
|
|
conn.SendNow("\000\000\356", 3);//End this track Object
|
|
}
|
|
|
|
///\brief Determines the "packed" size of a meta object
|
|
unsigned int Meta::getSendLen() {
|
|
unsigned int dataLen = 16 + (vod ? 14 : 0) + (live ? 15 : 0) + (merged ? 17 : 0) + (bufferWindow ? 24 : 0) + 21;
|
|
for (std::map<unsigned int, Track>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
|
dataLen += it->second.getSendLen();
|
|
}
|
|
return dataLen + 8; //add 8 bytes header
|
|
}
|
|
|
|
///\brief Writes a meta object to a pointer
|
|
void Meta::writeTo(char * p) {
|
|
int dataLen = getSendLen() - 8; //strip 8 bytes header
|
|
writePointer(p, DTSC::Magic_Header, 4);
|
|
writePointer(p, convertInt(dataLen), 4);
|
|
writePointer(p, "\340\000\006tracks\340", 10);
|
|
for (std::map<unsigned int, Track>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
|
it->second.writeTo(p);
|
|
}
|
|
writePointer(p, "\000\000\356", 3);//End tracks object
|
|
if (vod) {
|
|
writePointer(p, "\000\003vod\001", 6);
|
|
writePointer(p, convertLongLong(1), 8);
|
|
}
|
|
if (live) {
|
|
writePointer(p, "\000\004live\001", 7);
|
|
writePointer(p, convertLongLong(1), 8);
|
|
}
|
|
if (merged) {
|
|
writePointer(p, "\000\006merged\001", 9);
|
|
writePointer(p, convertLongLong(1), 8);
|
|
}
|
|
if (bufferWindow) {
|
|
writePointer(p, "\000\015buffer_window\001", 16);
|
|
writePointer(p, convertLongLong(bufferWindow), 8);
|
|
}
|
|
writePointer(p, "\000\012moreheader\001", 13);
|
|
writePointer(p, convertLongLong(moreheader), 8);
|
|
writePointer(p, "\000\000\356", 3);//End global object
|
|
}
|
|
|
|
///\brief Writes a meta object to a socket
|
|
void Meta::send(Socket::Connection & conn) {
|
|
int dataLen = getSendLen() - 8; //strip 8 bytes header
|
|
conn.SendNow(DTSC::Magic_Header, 4);
|
|
conn.SendNow(convertInt(dataLen), 4);
|
|
conn.SendNow("\340\000\006tracks\340", 10);
|
|
for (std::map<unsigned int, Track>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
|
it->second.send(conn);
|
|
}
|
|
conn.SendNow("\000\000\356", 3);//End tracks object
|
|
if (vod) {
|
|
conn.SendNow("\000\003vod\001", 6);
|
|
conn.SendNow(convertLongLong(1), 8);
|
|
}
|
|
if (live) {
|
|
conn.SendNow("\000\004live\001", 7);
|
|
conn.SendNow(convertLongLong(1), 8);
|
|
}
|
|
if (merged) {
|
|
conn.SendNow("\000\006merged\001", 9);
|
|
conn.SendNow(convertLongLong(1), 8);
|
|
}
|
|
if (bufferWindow) {
|
|
conn.SendNow("\000\015buffer_window\001", 16);
|
|
conn.SendNow(convertLongLong(bufferWindow), 8);
|
|
}
|
|
conn.SendNow("\000\012moreheader\001", 13);
|
|
conn.SendNow(convertLongLong(moreheader), 8);
|
|
conn.SendNow("\000\000\356", 3);//End global object
|
|
}
|
|
|
|
///\brief Converts a track to a JSON::Value
|
|
JSON::Value Track::toJSON() {
|
|
JSON::Value result;
|
|
std::string tmp;
|
|
tmp.reserve(fragments.size() * 11);
|
|
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
|
|
tmp.append(it->getData(), 11);
|
|
}
|
|
result["fragments"] = tmp;
|
|
tmp = "";
|
|
tmp.reserve(keys.size() * 16);
|
|
for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++) {
|
|
tmp.append(it->getData(), 16);
|
|
}
|
|
result["keys"] = tmp;
|
|
tmp = "";
|
|
tmp.reserve(keySizes.size() * 4);
|
|
for (unsigned int i = 0; i < keySizes.size(); i++){
|
|
tmp += (char)((keySizes[i] >> 24));
|
|
tmp += (char)((keySizes[i] >> 16));
|
|
tmp += (char)((keySizes[i] >> 8));
|
|
tmp += (char)(keySizes[i]);
|
|
}
|
|
result["keysizes"] = tmp;
|
|
tmp = "";
|
|
tmp.reserve(parts.size() * 9);
|
|
for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) {
|
|
tmp.append(it->getData(), 9);
|
|
}
|
|
result["parts"] = tmp;
|
|
result["trackid"] = trackID;
|
|
result["firstms"] = (long long)firstms;
|
|
result["lastms"] = (long long)lastms;
|
|
result["bps"] = bps;
|
|
if (missedFrags) {
|
|
result["missed_frags"] = missedFrags;
|
|
}
|
|
result["codec"] = codec;
|
|
result["type"] = type;
|
|
result["init"] = init;
|
|
if (type == "audio") {
|
|
result["rate"] = rate;
|
|
result["size"] = size;
|
|
result["channels"] = channels;
|
|
} else if (type == "video") {
|
|
result["width"] = width;
|
|
result["height"] = height;
|
|
result["fpks"] = fpks;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///\brief Converts a meta object to a JSON::Value
|
|
JSON::Value Meta::toJSON() {
|
|
JSON::Value result;
|
|
for (std::map<unsigned int, Track>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
|
result["tracks"][it->second.getWritableIdentifier()] = it->second.toJSON();
|
|
}
|
|
if (vod) {
|
|
result["vod"] = 1ll;
|
|
}
|
|
if (live) {
|
|
result["live"] = 1ll;
|
|
}
|
|
if (merged) {
|
|
result["merged"] = 1ll;
|
|
}
|
|
if (bufferWindow) {
|
|
result["buffer_window"] = bufferWindow;
|
|
}
|
|
result["moreheader"] = moreheader;
|
|
return result;
|
|
}
|
|
|
|
///\brief Converts a meta object to a human readable string
|
|
///\param str The stringstream to append to
|
|
///\param indent the amount of indentation needed
|
|
///\param verbosity How verbose the output needs to be
|
|
void Meta::toPrettyString(std::ostream & str, int indent, int verbosity) {
|
|
for (std::map<unsigned int, Track>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
|
it->second.toPrettyString(str, indent, verbosity);
|
|
}
|
|
if (vod) {
|
|
str << std::string(indent, ' ') << "Video on Demand" << std::endl;
|
|
}
|
|
if (live) {
|
|
str << std::string(indent, ' ') << "Live" << std::endl;
|
|
}
|
|
if (merged) {
|
|
str << std::string(indent, ' ') << "Merged file" << std::endl;
|
|
}
|
|
if (bufferWindow) {
|
|
str << std::string(indent, ' ') << "Buffer Window: " << bufferWindow << std::endl;
|
|
}
|
|
str << std::string(indent, ' ') << "More Header: " << moreheader << std::endl;
|
|
}
|
|
|
|
///\brief Resets a meta object, removes all unimportant meta values
|
|
void Meta::reset() {
|
|
for (std::map<unsigned int, Track>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
|
it->second.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|