New Meta commit

This commit is contained in:
Phencys 2021-04-21 18:10:03 +02:00 committed by Thulinma
parent fccf66fba2
commit 2b99f2f5ea
183 changed files with 13333 additions and 14421 deletions

2
.gitignore vendored
View file

@ -37,6 +37,8 @@ libtool
server.html* server.html*
*.js.h *.js.h
*.css.h *.css.h
noffmpeg.h
noh264.h
.dirstamp .dirstamp
*.orig *.orig
*.lock *.lock

View file

@ -9,6 +9,8 @@ if(COMMAND cmake_policy)
cmake_policy(SET CMP0003 NEW) cmake_policy(SET CMP0003 NEW)
endif(COMMAND cmake_policy) endif(COMMAND cmake_policy)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++98")
SET(SOURCE_DIR ${PROJECT_SOURCE_DIR}) SET(SOURCE_DIR ${PROJECT_SOURCE_DIR})
SET(BINARY_DIR ${PROJECT_BINARY_DIR}) SET(BINARY_DIR ${PROJECT_BINARY_DIR})
set( CMAKE_EXPORT_COMPILE_COMMANDS ON ) #For YCM support set( CMAKE_EXPORT_COMPILE_COMMANDS ON ) #For YCM support
@ -132,6 +134,7 @@ endif()
######################################## ########################################
message("Builing release ${RELEASE} for version ${PACKAGE_VERSION} @ debug level ${DEBUG}") message("Builing release ${RELEASE} for version ${PACKAGE_VERSION} @ debug level ${DEBUG}")
add_definitions(-g -funsigned-char -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DDEBUG=${DEBUG} -DPACKAGE_VERSION=${PACKAGE_VERSION} -DRELEASE=${RELEASE}) add_definitions(-g -funsigned-char -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DDEBUG=${DEBUG} -DPACKAGE_VERSION=${PACKAGE_VERSION} -DRELEASE=${RELEASE})
add_definitions(-Wall -Wno-sign-compare -Wparentheses)
######################################## ########################################
# MistLib - Header Files # # MistLib - Header Files #
@ -145,6 +148,8 @@ set(libHeaders
lib/bitstream.h lib/bitstream.h
lib/certificate.h lib/certificate.h
lib/checksum.h lib/checksum.h
lib/cmaf.h
lib/comms.h
lib/config.h lib/config.h
lib/defines.h lib/defines.h
lib/dtls_srtp_handshake.h lib/dtls_srtp_handshake.h
@ -168,7 +173,6 @@ set(libHeaders
lib/nal.h lib/nal.h
lib/ogg.h lib/ogg.h
lib/procs.h lib/procs.h
lib/rijndael.h
lib/rtmpchunks.h lib/rtmpchunks.h
lib/rtp_fec.h lib/rtp_fec.h
lib/rtp.h lib/rtp.h
@ -207,11 +211,12 @@ add_library (mist
lib/encode.cpp lib/encode.cpp
lib/bitfields.cpp lib/bitfields.cpp
lib/bitstream.cpp lib/bitstream.cpp
lib/cmaf.cpp
lib/comms.cpp
lib/certificate.cpp lib/certificate.cpp
lib/config.cpp lib/config.cpp
lib/dtls_srtp_handshake.cpp lib/dtls_srtp_handshake.cpp
lib/dtsc.cpp lib/dtsc.cpp
lib/dtscmeta.cpp
lib/encryption.cpp lib/encryption.cpp
lib/flv_tag.cpp lib/flv_tag.cpp
lib/h264.cpp lib/h264.cpp
@ -230,7 +235,6 @@ add_library (mist
lib/nal.cpp lib/nal.cpp
lib/ogg.cpp lib/ogg.cpp
lib/procs.cpp lib/procs.cpp
lib/rijndael.cpp
lib/rtmpchunks.cpp lib/rtmpchunks.cpp
lib/rtp_fec.cpp lib/rtp_fec.cpp
lib/rtp.cpp lib/rtp.cpp
@ -356,7 +360,8 @@ macro(makeUtil utilName utilFile)
) )
endmacro() endmacro()
makeUtil(Stats stats) #makeUtil(Stats stats)
makeUtil(META meta)
makeUtil(RAX rax) makeUtil(RAX rax)
makeUtil(AMF amf) makeUtil(AMF amf)
makeUtil(Certbot certbot) makeUtil(Certbot certbot)
@ -365,6 +370,14 @@ if (DEFINED LOAD_BALANCE )
endif() endif()
#LTS_END #LTS_END
add_executable(MistTranslateH264
src/analysers/h264_translate.cpp
${BINARY_DIR}/mist/.headers
)
target_link_libraries(MistTranslateH264
mist
)
######################################## ########################################
# MistServer - Inputs # # MistServer - Inputs #
######################################## ########################################
@ -396,7 +409,6 @@ endmacro()
makeInput(HLS hls) makeInput(HLS hls)
makeInput(DTSC dtsc) makeInput(DTSC dtsc)
makeInput(DTSCCrypt dtsccrypt)
makeInput(MP3 mp3) makeInput(MP3 mp3)
makeInput(FLV flv) makeInput(FLV flv)
if (DEFINED WITH_AV ) if (DEFINED WITH_AV )
@ -432,6 +444,9 @@ macro(makeOutput outputName format)
if (";${ARGN};" MATCHES ";ts;") if (";${ARGN};" MATCHES ";ts;")
SET(tsOutput src/output/output_ts_base.cpp) SET(tsOutput src/output/output_ts_base.cpp)
endif() endif()
if (";${ARGN};" MATCHES ";jpg;")
SET(tsOutput generated/noffmpeg.h generated/noh264.h)
endif()
add_executable(MistOut${outputName} add_executable(MistOut${outputName}
src/output/mist_out.cpp src/output/mist_out.cpp
src/output/output.cpp src/output/output.cpp
@ -439,6 +454,7 @@ macro(makeOutput outputName format)
src/io.cpp src/io.cpp
${httpOutput} ${httpOutput}
${tsOutput} ${tsOutput}
${mp4Output}
${BINARY_DIR}/mist/.headers ${BINARY_DIR}/mist/.headers
) )
set_target_properties(MistOut${outputName} set_target_properties(MistOut${outputName}
@ -455,21 +471,23 @@ endmacro()
makeOutput(RTMP rtmp) makeOutput(RTMP rtmp)
makeOutput(DTSC dtsc) makeOutput(DTSC dtsc)
makeOutput(OGG progressive_ogg http) makeOutput(OGG ogg http)
makeOutput(FLV progressive_flv http) makeOutput(FLV flv http)
makeOutput(HTTPMinimalServer http_minimalserver http) makeOutput(HTTPMinimalServer http_minimalserver http)
makeOutput(MP4 progressive_mp4 http) makeOutput(MP4 mp4 http)
makeOutput(MP3 progressive_mp3 http) makeOutput(MP3 mp3 http)
makeOutput(H264 h264 http) makeOutput(H264 h264 http)
makeOutput(HSS hss http)
makeOutput(HDS hds http) makeOutput(HDS hds http)
makeOutput(SRT srt http) makeOutput(SRT srt http)
makeOutput(JSON json http) makeOutput(JSON json http)
if (DEFINED WITH_JPG )
makeOutput(JPG jpg http jpg)
endif()
makeOutput(TS ts ts) makeOutput(TS ts ts)
makeOutput(HTTPTS httpts http ts) makeOutput(HTTPTS httpts http ts)
makeOutput(HLS hls http ts) makeOutput(HLS hls http ts)
makeOutput(CMAF cmaf http)#LTS
makeOutput(EBML ebml) makeOutput(EBML ebml)
makeOutput(Push push)#LTS
makeOutput(RTSP rtsp)#LTS makeOutput(RTSP rtsp)#LTS
makeOutput(WAV wav)#LTS makeOutput(WAV wav)#LTS
makeOutput(WebRTC webrtc http)#LTS makeOutput(WebRTC webrtc http)#LTS
@ -501,7 +519,6 @@ target_link_libraries(MistProcMKVExec mist)
if (NOT DEFINED NOSSL ) if (NOT DEFINED NOSSL )
makeOutput(HTTPS https)#LTS makeOutput(HTTPS https)#LTS
endif() endif()
makeOutput(DASH dash_mp4 http)#LTS
if (DEFINED WITH_SANITY ) if (DEFINED WITH_SANITY )
makeOutput(SanityCheck sanitycheck)#LTS makeOutput(SanityCheck sanitycheck)#LTS
@ -586,6 +603,17 @@ else()
endif() endif()
endif() endif()
########################################
# RelAccX Sampler #
########################################
add_executable(RelAccXSampler
src/relaccxsampler.cpp
${BINARY_DIR}/mist/.headers
)
target_link_libraries(RelAccXSampler
mist
)
######################################## ########################################
# Embed Code # # Embed Code #
######################################## ########################################
@ -658,6 +686,18 @@ add_custom_command(OUTPUT generated/skin_videojs.css.h
DEPENDS sourcery ${SOURCE_DIR}/embed/skins/video-js.css DEPENDS sourcery ${SOURCE_DIR}/embed/skins/video-js.css
) )
########################################
# JPG output #
########################################
add_custom_command(OUTPUT generated/noffmpeg.h
COMMAND ./sourcery ${SOURCE_DIR}/src/output/noffmpeg.jpg noffmpeg generated/noffmpeg.h
DEPENDS sourcery ${SOURCE_DIR}/src/output/noffmpeg.jpg
)
add_custom_command(OUTPUT generated/noh264.h
COMMAND ./sourcery ${SOURCE_DIR}/src/output/noh264.jpg noh264 generated/noh264.h
DEPENDS sourcery ${SOURCE_DIR}/src/output/noh264.jpg
)
######################################## ########################################
# Local Settings Page # # Local Settings Page #
######################################## ########################################
@ -742,15 +782,6 @@ add_custom_target(clean-all
######################################## ########################################
# Tests # # Tests #
######################################## ########################################
add_executable(aes_ctr128
test/aes_ctr128.cpp
${BINARY_DIR}/mist/.headers
)
target_link_libraries(aes_ctr128
mist
)
add_test(AESTest COMMAND aes_ctr128)
add_executable(urltest test/url.cpp ${BINARY_DIR}/mist/.headers) add_executable(urltest test/url.cpp ${BINARY_DIR}/mist/.headers)
target_link_libraries(urltest mist) target_link_libraries(urltest mist)
add_test(URLTest COMMAND urltest) add_test(URLTest COMMAND urltest)
@ -768,4 +799,7 @@ target_link_libraries(jsontest mist)
add_test(JSONTest COMMAND jsontest) add_test(JSONTest COMMAND jsontest)
add_executable(resolvetest test/resolve.cpp ${BINARY_DIR}/mist/.headers) add_executable(resolvetest test/resolve.cpp ${BINARY_DIR}/mist/.headers)
target_link_libraries(resolvetest mist) target_link_libraries(resolvetest mist)
add_executable(bitwritertest test/bitwriter.cpp ${BINARY_DIR}/mist/.headers)
target_link_libraries(bitwritertest mist)
add_test(BitWriterTest COMMAND bitwritertest)

View file

@ -1,3 +1,4 @@
#include "bitfields.h"
#include "bitstream.h" #include "bitstream.h"
#include "defines.h" #include "defines.h"
#include <stdlib.h> #include <stdlib.h>
@ -142,62 +143,40 @@ namespace Utils{
long long unsigned int bitstream::peekUExpGolomb(){return golombPeeker() - 1;} long long unsigned int bitstream::peekUExpGolomb(){return golombPeeker() - 1;}
bitWriter::bitWriter(){ bitWriter::bitWriter(){bitSize = 0;}
dataBuffer = NULL;
bufferSize = 0; size_t bitWriter::size() const{return bitSize;}
reallocate(0);
dataSize = 0; void bitWriter::append(const std::string &val){
for (size_t i = 0; i < val.size(); i++){append(val[i]);}
} }
bitWriter::~bitWriter(){ void bitWriter::append(uint64_t val, size_t bitLength){
if (dataBuffer != NULL){free(dataBuffer);} static char buf[9];
}
void bitWriter::reallocate(size_t newSize){ uint32_t byteLength = ((bitSize + bitLength) / 8) + 1;
size_t sizeBefore = bufferSize / 8; while (byteLength > p.size()){p.append("", 1);}
char *tmp;
if (dataBuffer != NULL){ int bitShift = (64 - bitLength) - (bitSize % 8);
tmp = (char *)realloc(dataBuffer, (newSize / 8) + 1);
if (bitShift >= 0){
Bit::htobll(buf, val << bitShift);
}else{ }else{
tmp = (char *)malloc((newSize / 8) + 1); Bit::htobll(buf, val >> (bitShift * -1));
} buf[8] = ((val << (8 + bitShift)) & 0xFF);
if (tmp){
dataBuffer = tmp;
bufferSize = ((newSize / 8) + 1) * 8;
memset(dataBuffer + sizeBefore, 0x00, (bufferSize / 8) - sizeBefore);
}else{
FAIL_MSG("Could not reallocate!!");
} }
size_t adjustableBits = (bitSize % 8) + bitLength;
size_t adjustableBytes = adjustableBits / 8 + (adjustableBits % 8 ? 1 : 0);
for (int i = 0; i < adjustableBytes; i++){p[bitSize / 8 + i] |= buf[i];}
bitSize += bitLength;
} }
size_t bitWriter::size(){return dataSize;} void bitWriter::clear(){
p.assign("", 0);
void bitWriter::append(uint64_t value, size_t bitLength){ bitSize = 0;
if (dataSize + bitLength > bufferSize){reallocate(dataSize + bitLength);}
int64_t fullShift = (bitLength / 8) * 8;
uint64_t firstMask = ((0x01ull << (bitLength % 8)) - 1) << fullShift;
appendData(((value & firstMask) >> fullShift), bitLength - fullShift);
while (fullShift > 0){
fullShift -= 8;
uint64_t mask = (0xFFull) << fullShift;
appendData((value & mask) >> fullShift, 8);
}
}
void bitWriter::appendData(uint8_t data, size_t len){
size_t byteOffset = dataSize / 8;
size_t bitOffset = dataSize % 8;
if (len <= 8 - bitOffset){
dataBuffer[byteOffset] |= (data << (8 - bitOffset - len));
dataSize += len;
}else{
size_t shift = (len - (8 - bitOffset));
dataBuffer[byteOffset] |= (data >> shift);
dataSize += (len - shift);
appendData(data, shift);
}
} }
size_t bitWriter::UExpGolombEncodedSize(uint64_t value){ size_t bitWriter::UExpGolombEncodedSize(uint64_t value){

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "defines.h" #include "defines.h"
#include "util.h"
#include <string> #include <string>
namespace Utils{ namespace Utils{
@ -52,23 +53,20 @@ namespace Utils{
class bitWriter{ class bitWriter{
public: public:
bitWriter(); bitWriter();
~bitWriter(); size_t size() const;
size_t size(); void append(const std::string &val);
void append(uint64_t value, size_t bitLength); void append(uint64_t val, size_t bitLength = 8);
void appendExpGolomb(int64_t value); void appendExpGolomb(int64_t value);
void appendUExpGolomb(uint64_t value); void appendUExpGolomb(uint64_t value);
static size_t UExpGolombEncodedSize(uint64_t value); static size_t UExpGolombEncodedSize(uint64_t value);
std::string str(){return std::string(dataBuffer, (dataSize / 8) + (dataSize % 8 ? 1 : 0));} std::string str(){return std::string(p, (bitSize / 8) + (bitSize % 8 ? 1 : 0));}
void clear();
protected: protected:
void reallocate(size_t newSize); // void appendData(uint8_t data, size_t len);
void appendData(uint8_t data, size_t len); size_t bitSize;
Util::ResizeablePointer p;
char *dataBuffer;
// NOTE: ALL SIZES IN BITS!
size_t bufferSize;
size_t dataSize;
}; };
class bitstreamLSBF{ class bitstreamLSBF{

View file

@ -213,7 +213,6 @@ std::string Certificate::getFingerprintSha256(){
uint8_t fingerprint_raw[32] ={}; uint8_t fingerprint_raw[32] ={};
uint8_t fingerprint_hex[128] ={}; uint8_t fingerprint_hex[128] ={};
mbedtls_md_type_t hash_type = MBEDTLS_MD_SHA256;
mbedtls_sha256(cert.raw.p, cert.raw.len, fingerprint_raw, 0); mbedtls_sha256(cert.raw.p, cert.raw.len, fingerprint_raw, 0);

302
lib/cmaf.cpp Normal file
View file

@ -0,0 +1,302 @@
#include "cmaf.h"
namespace CMAF{
size_t payloadSize(const DTSC::Meta &M, size_t track, size_t fragment){
DTSC::Fragments fragments(M.fragments(track));
DTSC::Keys keys(M.keys(track));
DTSC::Parts parts(M.parts(track));
size_t firstKey = fragments.getFirstKey(fragment);
size_t endKey = keys.getEndValid();
if (fragment + 1 < fragments.getEndValid()){endKey = fragments.getFirstKey(fragment + 1);}
size_t firstPart = keys.getFirstPart(firstKey);
size_t endPart = parts.getEndValid();
if (endKey != keys.getEndValid()){endPart = keys.getFirstPart(endKey);}
size_t payloadSize = 0;
for (size_t i = firstPart; i < endPart; i++){payloadSize += parts.getSize(i);}
return payloadSize;
}
size_t trackHeaderSize(const DTSC::Meta &M, size_t track){
// EDTS Box needed? + 36
size_t res = 36 + 8 + 108 + 8 + 92 + 8 + 32 + 33 + 44 + 8 + 20 + 16 + 16 + 16 + 40;
res += M.getTrackIdentifier(track).size();
// Type-specific boxes
std::string tType = M.getType(track);
if (tType == "video"){res += 20 + 16 + 86 + 16 + 8 + M.getInit(track).size();}
if (tType == "audio"){
res += 16 + 16 + 36 + 35 + (M.getInit(track).size() ? 2 + M.getInit(track).size() : 0);
}
if (tType == "meta"){res += 12 + 16 + 64;}
if (M.getVod()){res += 16;}
return res;
}
std::string trackHeader(const DTSC::Meta &M, size_t track){
std::string tType = M.getType(track);
std::stringstream header;
MP4::FTYP ftypBox;
ftypBox.setMajorBrand("isom");
ftypBox.setCompatibleBrands("cmfc", 0);
ftypBox.setCompatibleBrands("isom", 1);
ftypBox.setCompatibleBrands("dash", 2);
ftypBox.setCompatibleBrands("iso9", 3);
header.write(ftypBox.asBox(), ftypBox.boxedSize());
MP4::MOOV moovBox;
MP4::MVHD mvhdBox(0);
mvhdBox.setTrackID(track + 2); // This value needs to point to an unused trackid
moovBox.setContent(mvhdBox, 0);
MP4::TRAK trakBox;
MP4::TKHD tkhdBox(M, track);
tkhdBox.setDuration(0);
trakBox.setContent(tkhdBox, 0);
MP4::MDIA mdiaBox;
MP4::MDHD mdhdBox(0, M.getLang(track));
mdiaBox.setContent(mdhdBox, 0);
MP4::HDLR hdlrBox(tType, M.getTrackIdentifier(track));
mdiaBox.setContent(hdlrBox, 1);
MP4::MINF minfBox;
if (tType == "video"){
MP4::VMHD vmhdBox;
vmhdBox.setFlags(1);
minfBox.setContent(vmhdBox, 0);
}else if (tType == "audio"){
MP4::SMHD smhdBox;
minfBox.setContent(smhdBox, 0);
}else{
MP4::NMHD nmhdBox;
minfBox.setContent(nmhdBox, 0);
}
MP4::DINF dinfBox;
MP4::DREF drefBox;
dinfBox.setContent(drefBox, 0);
minfBox.setContent(dinfBox, 1);
MP4::STBL stblBox;
// Add STSD box
MP4::STSD stsdBox(0);
if (tType == "video"){
MP4::VisualSampleEntry sampleEntry(M, track);
stsdBox.setEntry(sampleEntry, 0);
}else if (tType == "audio"){
MP4::AudioSampleEntry sampleEntry(M, track);
stsdBox.setEntry(sampleEntry, 0);
}else if (tType == "meta"){
MP4::TextSampleEntry sampleEntry(M, track);
MP4::FontTableBox ftab;
sampleEntry.setFontTableBox(ftab);
stsdBox.setEntry(sampleEntry, 0);
}
stblBox.setContent(stsdBox, 0);
MP4::STTS sttsBox(0);
stblBox.setContent(sttsBox, 1);
MP4::STSC stscBox(0);
stblBox.setContent(stscBox, 2);
MP4::STSZ stszBox(0);
stblBox.setContent(stszBox, 3);
MP4::STCO stcoBox(0);
stblBox.setContent(stcoBox, 4);
minfBox.setContent(stblBox, 2);
mdiaBox.setContent(minfBox, 2);
trakBox.setContent(mdiaBox, 1);
moovBox.setContent(trakBox, 1);
MP4::MVEX mvexBox;
if (M.getVod()){
MP4::MEHD mehdBox;
mehdBox.setFragmentDuration(M.getDuration(track));
mvexBox.setContent(mehdBox, 0);
}
MP4::TREX trexBox(track + 1);
trexBox.setDefaultSampleDuration(1000);
mvexBox.setContent(trexBox, M.getVod() ? 1 : 0);
moovBox.setContent(mvexBox, 2);
header.write(moovBox.asBox(), moovBox.boxedSize());
if (M.getVod()){
DTSC::Fragments fragments(M.fragments(track));
DTSC::Keys keys(M.keys(track));
DTSC::Parts parts(M.parts(track));
MP4::SIDX sidxBox;
sidxBox.setReferenceID(track + 1);
sidxBox.setTimescale(1000);
sidxBox.setEarliestPresentationTime(keys.getTime(0) + parts.getOffset(0) - M.getFirstms(track));
for (size_t i = 0; i < fragments.getEndValid(); i++){
size_t firstKey = fragments.getFirstKey(i);
size_t endKey =
((i + 1 < fragments.getEndValid()) ? fragments.getFirstKey(i + 1) : keys.getEndValid());
MP4::sidxReference refItem;
refItem.referencedSize = payloadSize(M, track, i) + fragmentHeaderSize(M, track, i) + 8;
refItem.subSegmentDuration =
(endKey == keys.getEndValid() ? M.getLastms(track) : keys.getTime(endKey)) - keys.getTime(firstKey);
refItem.sapStart = true;
refItem.sapType = 16;
refItem.sapDeltaTime = 0;
refItem.referenceType = 0;
sidxBox.setReference(refItem, i);
}
header.write(sidxBox.asBox(), sidxBox.boxedSize());
}
return header.str();
}
class sortPart{
public:
uint64_t time;
size_t partIndex;
size_t bytePos;
bool operator<(const sortPart &rhs) const{return time < rhs.time;}
};
size_t fragmentHeaderSize(const DTSC::Meta &M, size_t track, size_t fragment){
uint64_t tmpRes = 8 + 16 + 32 + 20;
DTSC::Fragments fragments(M.fragments(track));
DTSC::Keys keys(M.keys(track));
DTSC::Parts parts(M.parts(track));
size_t firstKey = fragments.getFirstKey(fragment);
size_t firstPart = keys.getFirstPart(firstKey);
size_t endPart = parts.getEndValid();
if (fragment + 1 < fragments.getEndValid()){
endPart = keys.getFirstPart(fragments.getFirstKey(fragment + 1));
}
tmpRes += 24 + ((endPart - firstPart) * 12);
return tmpRes;
}
std::string fragmentHeader(const DTSC::Meta &M, size_t track, size_t fragment){
DTSC::Fragments fragments(M.fragments(track));
DTSC::Keys keys(M.keys(track));
DTSC::Parts parts(M.parts(track));
size_t firstKey = fragments.getFirstKey(fragment);
size_t endKey = keys.getEndValid();
if (fragment + 1 < fragments.getEndValid()){endKey = fragments.getFirstKey(fragment + 1);}
std::stringstream header;
if (M.getLive()){
MP4::SIDX sidxBox;
sidxBox.setTimescale(1000);
sidxBox.setEarliestPresentationTime(keys.getTime(firstKey));
MP4::sidxReference refItem;
refItem.referencedSize = 230000;
refItem.subSegmentDuration = keys.getTime(endKey) - keys.getTime(firstKey);
refItem.sapStart = true;
refItem.sapType = 16;
refItem.sapDeltaTime = 0;
refItem.referenceType = 0;
sidxBox.setReference(refItem, 0);
sidxBox.setReferenceID(1);
header.write(sidxBox.asBox(), sidxBox.boxedSize());
}
MP4::MOOF moofBox;
MP4::MFHD mfhdBox(fragment + 1);
moofBox.setContent(mfhdBox, 0);
size_t firstPart = keys.getFirstPart(firstKey);
size_t endPart = parts.getEndValid();
if (fragment + 1 < fragments.getEndValid()){
endPart = keys.getFirstPart(fragments.getFirstKey(fragment + 1));
}
std::set<sortPart> trunOrder;
uint64_t relativeOffset = fragmentHeaderSize(M, track, fragment) + 8;
sortPart temp;
temp.time = keys.getTime(firstKey);
temp.partIndex = keys.getFirstPart(firstKey);
temp.bytePos = relativeOffset;
for (size_t p = firstPart; p < endPart; p++){
trunOrder.insert(temp);
temp.time += parts.getDuration(p);
temp.partIndex++;
temp.bytePos += parts.getSize(p);
}
MP4::TRAF trafBox;
MP4::TFHD tfhdBox;
tfhdBox.setFlags(MP4::tfhdSampleFlag | MP4::tfhdBaseIsMoof | MP4::tfhdSampleDesc);
tfhdBox.setTrackID(track + 1);
tfhdBox.setDefaultSampleDuration(444);
tfhdBox.setDefaultSampleSize(444);
tfhdBox.setDefaultSampleFlags((M.getType(track) == "video") ? (MP4::noIPicture | MP4::noKeySample)
: (MP4::isIPicture | MP4::isKeySample));
tfhdBox.setSampleDescriptionIndex(0);
trafBox.setContent(tfhdBox, 0);
MP4::TFDT tfdtBox;
if (M.getVod()){
tfdtBox.setBaseMediaDecodeTime(M.getTimeForFragmentIndex(track, fragment) - M.getFirstms(track));
}else{
tfdtBox.setBaseMediaDecodeTime(M.getTimeForFragmentIndex(track, fragment));
}
trafBox.setContent(tfdtBox, 1);
MP4::TRUN trunBox;
trunBox.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleSize |
MP4::trunsampleDuration | MP4::trunsampleOffsets);
// The value set here, will be updated afterwards to the correct value
trunBox.setDataOffset(trunOrder.begin()->bytePos);
trunBox.setFirstSampleFlags(MP4::isIPicture | MP4::isKeySample);
size_t trunOffset = 0;
for (std::set<sortPart>::iterator it = trunOrder.begin(); it != trunOrder.end(); it++){
MP4::trunSampleInformation sampleInfo;
sampleInfo.sampleSize = parts.getSize(it->partIndex);
sampleInfo.sampleDuration = parts.getDuration(it->partIndex);
sampleInfo.sampleOffset = parts.getOffset(it->partIndex);
trunBox.setSampleInformation(sampleInfo, trunOffset++);
}
trafBox.setContent(trunBox, 2);
moofBox.setContent(trafBox, 1);
header.write(moofBox.asBox(), moofBox.boxedSize());
return header.str();
}
}// namespace CMAF

12
lib/cmaf.h Normal file
View file

@ -0,0 +1,12 @@
#include "dtsc.h"
#include "mp4_dash.h"
#include "mp4_generic.h"
#include <set>
namespace CMAF{
size_t payloadSize(const DTSC::Meta &M, size_t track, size_t fragment);
size_t trackHeaderSize(const DTSC::Meta &M, size_t track);
std::string trackHeader(const DTSC::Meta &M, size_t track);
size_t fragmentHeaderSize(const DTSC::Meta &M, size_t track, size_t fragment);
std::string fragmentHeader(const DTSC::Meta &M, size_t track, size_t fragment);
}// namespace CMAF

440
lib/comms.cpp Normal file
View file

@ -0,0 +1,440 @@
#include "auth.h"
#include "bitfields.h"
#include "comms.h"
#include "defines.h"
#include "encode.h"
#include "procs.h"
#include "timing.h"
namespace Comms{
Comms::Comms(){
index = INVALID_RECORD_INDEX;
currentSize = 0;
master = false;
}
Comms::~Comms(){
if (index != INVALID_RECORD_INDEX){setStatus(COMM_STATUS_DISCONNECT);}
if (master){
if (dataPage.mapped){
finishAll();
dataPage.master = true;
}
sem.unlink();
}
sem.close();
}
void Comms::addCommonFields(){
dataAccX.addField("status", RAX_UINT);
dataAccX.addField("command", RAX_64UINT);
dataAccX.addField("timer", RAX_UINT);
dataAccX.addField("pid", RAX_32UINT);
dataAccX.addField("killtime", RAX_64UINT);
}
void Comms::commonFieldAccess(){
status = dataAccX.getFieldAccX("status");
command = dataAccX.getFieldAccX("command");
timer = dataAccX.getFieldAccX("timer");
pid = dataAccX.getFieldAccX("pid");
killTime = dataAccX.getFieldAccX("killtime");
}
size_t Comms::firstValid() const{
if (!master){return index;}
return dataAccX.getStartPos();
}
size_t Comms::endValid() const{
if (!master){return index + 1;}
return dataAccX.getEndPos();
}
void Comms::deleteFirst(){
if (!master){return;}
dataAccX.deleteRecords(1);
}
uint8_t Comms::getStatus() const{return status.uint(index);}
uint8_t Comms::getStatus(size_t idx) const{return (master ? status.uint(idx) : 0);}
void Comms::setStatus(uint8_t _status){status.set(_status, index);}
void Comms::setStatus(uint8_t _status, size_t idx){
if (!master){return;}
status.set(_status, idx);
}
uint64_t Comms::getCommand() const{return command.uint(index);}
uint64_t Comms::getCommand(size_t idx) const{return (master ? command.uint(idx) : 0);}
void Comms::setCommand(uint64_t _cmd){command.set(_cmd, index);}
void Comms::setCommand(uint64_t _cmd, size_t idx){
if (!master){return;}
command.set(_cmd, idx);
}
uint8_t Comms::getTimer() const{return timer.uint(index);}
uint8_t Comms::getTimer(size_t idx) const{return (master ? timer.uint(idx) : 0);}
void Comms::setTimer(uint8_t _timer){timer.set(_timer, index);}
void Comms::setTimer(uint8_t _timer, size_t idx){
if (!master){return;}
timer.set(_timer, idx);
}
uint32_t Comms::getPid() const{return pid.uint(index);}
uint32_t Comms::getPid(size_t idx) const{return (master ? pid.uint(idx) : 0);}
void Comms::setPid(uint32_t _pid){pid.set(_pid, index);}
void Comms::setPid(uint32_t _pid, size_t idx){
if (!master){return;}
pid.set(_pid, idx);
}
void Comms::kill(size_t idx, bool force){
if (!master){return;}
if (force){
Util::Procs::Murder(pid.uint(idx)); // hard kill
status.set(COMM_STATUS_INVALID, idx);
return;
}
uint64_t kTime = killTime.uint(idx);
uint64_t now = Util::bootSecs();
if (!kTime){
kTime = now;
killTime.set(kTime, idx);
}
if (now - kTime > 30){
Util::Procs::Murder(pid.uint(idx)); // hard kill
status.set(COMM_STATUS_INVALID, idx);
}else{
Util::Procs::Stop(pid.uint(idx)); // soft kill
}
}
void Comms::finishAll(){
if (!master){return;}
size_t c = 0;
do{
for (size_t i = firstValid(); i < endValid(); i++){
if (getStatus(i) == COMM_STATUS_INVALID){continue;}
setStatus(COMM_STATUS_DISCONNECT, i);
}
while (getStatus(firstValid()) == COMM_STATUS_INVALID){deleteFirst();}
}while (firstValid() < endValid() && ++c < 10);
}
void Comms::keepAlive(){
if (isAlive()){setTimer(0);}
}
bool Comms::isAlive() const{
if (!*this){return false;}
if (getStatus() == COMM_STATUS_INVALID){return false;}
if (getStatus() == COMM_STATUS_DISCONNECT){return false;}
return getTimer() < 126;
}
void Comms::setMaster(bool _master){
master = _master;
dataPage.master = _master;
}
Statistics::Statistics() : Comms(){sem.open(SEM_STATISTICS, O_CREAT | O_RDWR, ACCESSPERMS, 1);}
void Statistics::unload(){
if (index != INVALID_RECORD_INDEX){setStatus(COMM_STATUS_DISCONNECT);}
index = INVALID_RECORD_INDEX;
}
void Statistics::reload(bool _master, bool reIssue){
master = _master;
bool setFields = true;
if (!currentSize){currentSize = COMMS_STATISTICS_INITSIZE;}
dataPage.init(COMMS_STATISTICS, currentSize, false, false);
if (master){
if (dataPage.mapped){
setFields = false;
dataPage.master = true;
}else{
dataPage.init(COMMS_STATISTICS, currentSize, true);
}
}
if (!dataPage.mapped){
FAIL_MSG("Unable to open page " COMMS_STATISTICS);
return;
}
if (master){
dataAccX = Util::RelAccX(dataPage.mapped, false);
if (setFields){
addCommonFields();
dataAccX.addField("sync", RAX_UINT);
dataAccX.addField("now", RAX_64UINT);
dataAccX.addField("time", RAX_64UINT);
dataAccX.addField("lastsecond", RAX_64UINT);
dataAccX.addField("down", RAX_64UINT);
dataAccX.addField("up", RAX_64UINT);
dataAccX.addField("host", RAX_STRING, 16);
dataAccX.addField("stream", RAX_STRING, 100);
dataAccX.addField("connector", RAX_STRING, 20);
dataAccX.addField("crc", RAX_32UINT);
dataAccX.setRCount((currentSize - dataAccX.getOffset()) / dataAccX.getRSize());
dataAccX.setReady();
}
}else{
dataAccX = Util::RelAccX(dataPage.mapped);
if (index == INVALID_RECORD_INDEX || reIssue){
sem.wait();
for (index = 0; index < dataAccX.getEndPos(); ++index){
if (dataAccX.getInt("status", index) == COMM_STATUS_INVALID){
// Reverse! clear entry and claim it.
dataAccX.setInt("crc", 0, index);
dataAccX.setString("connector", "", index);
dataAccX.setString("stream", "", index);
dataAccX.setString("host", "", index);
dataAccX.setInt("up", 0, index);
dataAccX.setInt("down", 0, index);
dataAccX.setInt("lastsecond", 0, index);
dataAccX.setInt("time", 0, index);
dataAccX.setInt("now", 0, index);
dataAccX.setInt("sync", 0, index);
dataAccX.setInt("killtime", 0, index);
dataAccX.setInt("pid", 0, index);
dataAccX.setInt("timer", 0, index);
dataAccX.setInt("command", 0, index);
dataAccX.setInt("status", 0, index);
break;
}
}
if (index == dataAccX.getEndPos()){dataAccX.addRecords(1);}
sem.post();
}
}
commonFieldAccess();
sync = dataAccX.getFieldAccX("sync");
now = dataAccX.getFieldAccX("now");
time = dataAccX.getFieldAccX("time");
lastSecond = dataAccX.getFieldAccX("lastsecond");
down = dataAccX.getFieldAccX("down");
up = dataAccX.getFieldAccX("up");
host = dataAccX.getFieldAccX("host");
stream = dataAccX.getFieldAccX("stream");
connector = dataAccX.getFieldAccX("connector");
crc = dataAccX.getFieldAccX("crc");
}
uint8_t Statistics::getSync() const{return sync.uint(index);}
uint8_t Statistics::getSync(size_t idx) const{return (master ? sync.uint(idx) : 0);}
void Statistics::setSync(uint8_t _sync){sync.set(_sync, index);}
void Statistics::setSync(uint8_t _sync, size_t idx){
if (!master){return;}
sync.set(_sync, idx);
}
uint64_t Statistics::getNow() const{return now.uint(index);}
uint64_t Statistics::getNow(size_t idx) const{return (master ? now.uint(idx) : 0);}
void Statistics::setNow(uint64_t _now){now.set(_now, index);}
void Statistics::setNow(uint64_t _now, size_t idx){
if (!master){return;}
now.set(_now, idx);
}
uint64_t Statistics::getTime() const{return time.uint(index);}
uint64_t Statistics::getTime(size_t idx) const{return (master ? time.uint(idx) : 0);}
void Statistics::setTime(uint64_t _time){time.set(_time, index);}
void Statistics::setTime(uint64_t _time, size_t idx){
if (!master){return;}
time.set(_time, idx);
}
uint64_t Statistics::getLastSecond() const{return lastSecond.uint(index);}
uint64_t Statistics::getLastSecond(size_t idx) const{
return (master ? lastSecond.uint(idx) : 0);
}
void Statistics::setLastSecond(uint64_t _lastSecond){lastSecond.set(_lastSecond, index);}
void Statistics::setLastSecond(uint64_t _lastSecond, size_t idx){
if (!master){return;}
lastSecond.set(_lastSecond, idx);
}
uint64_t Statistics::getDown() const{return down.uint(index);}
uint64_t Statistics::getDown(size_t idx) const{return (master ? down.uint(idx) : 0);}
void Statistics::setDown(uint64_t _down){down.set(_down, index);}
void Statistics::setDown(uint64_t _down, size_t idx){
if (!master){return;}
down.set(_down, idx);
}
uint64_t Statistics::getUp() const{return up.uint(index);}
uint64_t Statistics::getUp(size_t idx) const{return (master ? up.uint(idx) : 0);}
void Statistics::setUp(uint64_t _up){up.set(_up, index);}
void Statistics::setUp(uint64_t _up, size_t idx){
if (!master){return;}
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) : "");}
void Statistics::setHost(std::string _host){host.set(_host, index);}
void Statistics::setHost(std::string _host, size_t idx){
if (!master){return;}
host.set(_host, idx);
}
std::string Statistics::getStream() const{return stream.string(index);}
std::string Statistics::getStream(size_t idx) const{return (master ? stream.string(idx) : "");}
void Statistics::setStream(std::string _stream){stream.set(_stream, index);}
void Statistics::setStream(std::string _stream, size_t idx){
if (!master){return;}
stream.set(_stream, idx);
}
std::string Statistics::getConnector() const{return connector.string(index);}
std::string Statistics::getConnector(size_t idx) const{
return (master ? connector.string(idx) : "");
}
void Statistics::setConnector(std::string _connector){connector.set(_connector, index);}
void Statistics::setConnector(std::string _connector, size_t idx){
if (!master){return;}
connector.set(_connector, idx);
}
uint32_t Statistics::getCRC() const{return crc.uint(index);}
uint32_t Statistics::getCRC(size_t idx) const{return (master ? crc.uint(idx) : 0);}
void Statistics::setCRC(uint32_t _crc){crc.set(_crc, index);}
void Statistics::setCRC(uint32_t _crc, size_t idx){
if (!master){return;}
crc.set(_crc, idx);
}
std::string Statistics::getSessId() const{return getSessId(index);}
std::string Statistics::getSessId(size_t idx) const{
char res[140];
memset(res, 0, 140);
std::string tmp = host.string(idx);
memcpy(res, tmp.c_str(), (tmp.size() > 16 ? 16 : tmp.size()));
tmp = stream.string(idx);
memcpy(res + 16, tmp.c_str(), (tmp.size() > 100 ? 100 : tmp.size()));
tmp = connector.string(idx);
memcpy(res + 116, tmp.c_str(), (tmp.size() > 20 ? 20 : tmp.size()));
Bit::htobl(res + 136, crc.uint(idx));
return Secure::md5(res, 140);
}
Users::Users() : Comms(){}
Users::Users(const Users &rhs) : Comms(){
if (rhs && rhs.isAlive()){
reload(rhs.streamName, (size_t)rhs.getTrack());
if (*this){
setKeyNum(rhs.getKeyNum());
setTrack(rhs.getTrack());
}
}
}
void Users::reload(const std::string &_streamName, bool _master, bool reIssue){
streamName = _streamName;
char semName[NAME_BUFFER_SIZE];
snprintf(semName, NAME_BUFFER_SIZE, SEM_USERS, streamName.c_str());
sem.open(semName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
master = _master;
if (!currentSize){currentSize = COMMS_USERS_INITSIZE;}
char userPageName[NAME_BUFFER_SIZE];
snprintf(userPageName, NAME_BUFFER_SIZE, COMMS_USERS, streamName.c_str());
bool newPage = false;
if (master){
dataPage.init(userPageName, currentSize, false, false);
if (dataPage){
dataPage.master = true;
}else{
dataPage.init(userPageName, currentSize, true);
newPage = true;
}
}else{
dataPage.init(userPageName, currentSize, false);
}
if (!dataPage.mapped){
HIGH_MSG("Unable to open page %s", userPageName);
return;
}
if (master){
if (newPage){
dataAccX = Util::RelAccX(dataPage.mapped, false);
addCommonFields();
dataAccX.addField("track", RAX_32UINT);
dataAccX.addField("keynum", RAX_32UINT);
dataAccX.setRCount((currentSize - dataAccX.getOffset()) / dataAccX.getRSize());
dataAccX.setReady();
}else{
dataAccX = Util::RelAccX(dataPage.mapped);
}
}else{
dataAccX = Util::RelAccX(dataPage.mapped);
if (index == INVALID_RECORD_INDEX || reIssue){
sem.wait();
for (index = 0; index < dataAccX.getEndPos(); ++index){
if (dataAccX.getInt("status", index) == COMM_STATUS_INVALID){
// Reverse! clear entry and claim it.
dataAccX.setInt("keynum", 0, index);
dataAccX.setInt("track", 0, index);
dataAccX.setInt("killtime", 0, index);
dataAccX.setInt("pid", 0, index);
dataAccX.setInt("timer", 0, index);
dataAccX.setInt("command", 0, index);
dataAccX.setInt("status", 0, index);
break;
}
}
if (index == dataAccX.getEndPos()){dataAccX.addRecords(1);}
sem.post();
}
}
commonFieldAccess();
track = dataAccX.getFieldAccX("track");
keyNum = dataAccX.getFieldAccX("keynum");
setPid(getpid());
}
void Users::reload(const std::string &_streamName, size_t idx, uint8_t initialState){
reload(_streamName);
if (dataPage.mapped){
setTrack(idx);
setStatus(initialState);
}
}
uint32_t Users::getTrack() const{return track.uint(index);}
uint32_t Users::getTrack(size_t idx) const{return (master ? track.uint(idx) : 0);}
void Users::setTrack(uint32_t _track){track.set(_track, index);}
void Users::setTrack(uint32_t _track, size_t idx){
if (!master){return;}
track.set(_track, idx);
}
size_t Users::getKeyNum() const{return keyNum.uint(index);}
size_t Users::getKeyNum(size_t idx) const{return (master ? keyNum.uint(idx) : 0);}
void Users::setKeyNum(size_t _keyNum){keyNum.set(_keyNum, index);}
void Users::setKeyNum(size_t _keyNum, size_t idx){
if (!master){return;}
keyNum.set(_keyNum, idx);
}
}// namespace Comms

194
lib/comms.h Normal file
View file

@ -0,0 +1,194 @@
#pragma once
#include "procs.h"
#include "shared_memory.h"
#include "util.h"
#define COMM_STATUS_DONOTTRACK 0x40
#define COMM_STATUS_SOURCE 0x80
#define COMM_STATUS_DISCONNECT 0xFE
#define COMM_STATUS_INVALID 0xFF
#define COMM_LOOP(comm, onActive, onDisconnect) \
{\
for (size_t id = comm.firstValid(); id != comm.endValid(); id++){\
if (comm.getStatus(id) == COMM_STATUS_INVALID){continue;}\
onActive; \
if (!Util::Procs::isRunning(comm.getPid(id))){\
comm.setStatus(COMM_STATUS_DISCONNECT, id); \
}\
if ((comm.getTimer(id) & 0x7F) >= 126 || comm.getStatus(id) == COMM_STATUS_DISCONNECT){\
onDisconnect; \
comm.setStatus(COMM_STATUS_INVALID, id); \
}\
if ((comm.getTimer(id) & 0x7F) <= 124){\
if ((comm.getTimer(id) & 0x7F) == 124){\
HIGH_MSG("Timeout occured for entry %zu, ignoring further timeout", id); \
}\
comm.setTimer(comm.getTimer(id) + 1, id); \
}\
}\
}
namespace Comms{
class Comms{
public:
Comms();
~Comms();
operator bool() const{return dataPage.mapped;}
void addCommonFields();
void commonFieldAccess();
size_t firstValid() const;
size_t endValid() const;
void deleteFirst();
uint8_t getStatus() const;
uint8_t getStatus(size_t idx) const;
void setStatus(uint8_t _status);
void setStatus(uint8_t _status, size_t idx);
uint64_t getCommand() const;
uint64_t getCommand(size_t idx) const;
void setCommand(uint64_t _cmd);
void setCommand(uint64_t _cmd, size_t idx);
uint8_t getTimer() const;
uint8_t getTimer(size_t idx) const;
void setTimer(uint8_t _timer);
void setTimer(uint8_t _timer, size_t idx);
uint32_t getPid() const;
uint32_t getPid(size_t idx) const;
void setPid(uint32_t _pid);
void setPid(uint32_t _pid, size_t idx);
void kill(size_t idx, bool force = false);
void finishAll();
void keepAlive();
bool isAlive() const;
void setMaster(bool _master);
const std::string &pageName() const{return dataPage.name;}
protected:
bool master;
size_t index;
size_t currentSize;
IPC::semaphore sem;
IPC::sharedPage dataPage;
Util::RelAccX dataAccX;
Util::FieldAccX status;
Util::FieldAccX command;
Util::FieldAccX timer;
Util::FieldAccX pid;
Util::FieldAccX killTime;
};
class Statistics : public Comms{
public:
Statistics();
operator bool() const{return dataPage.mapped && (master || index != INVALID_RECORD_INDEX);}
void unload();
void reload(bool _master = false, bool reIssue = false);
uint8_t getSync() const;
uint8_t getSync(size_t idx) const;
void setSync(uint8_t _sync);
void setSync(uint8_t _sync, size_t idx);
uint64_t getNow() const;
uint64_t getNow(size_t idx) const;
void setNow(uint64_t _now);
void setNow(uint64_t _now, size_t idx);
uint64_t getTime() const;
uint64_t getTime(size_t idx) const;
void setTime(uint64_t _time);
void setTime(uint64_t _time, size_t idx);
uint64_t getLastSecond() const;
uint64_t getLastSecond(size_t idx) const;
void setLastSecond(uint64_t _lastSecond);
void setLastSecond(uint64_t _lastSecond, size_t idx);
uint64_t getDown() const;
uint64_t getDown(size_t idx) const;
void setDown(uint64_t _down);
void setDown(uint64_t _down, size_t idx);
uint64_t getUp() const;
uint64_t getUp(size_t idx) const;
void setUp(uint64_t _up);
void setUp(uint64_t _up, size_t idx);
std::string getHost() const;
std::string getHost(size_t idx) const;
void setHost(std::string _host);
void setHost(std::string _host, size_t idx);
std::string getStream() const;
std::string getStream(size_t idx) const;
void setStream(std::string _stream);
void setStream(std::string _stream, size_t idx);
std::string getConnector() const;
std::string getConnector(size_t idx) const;
void setConnector(std::string _connector);
void setConnector(std::string _connector, size_t idx);
uint32_t getCRC() const;
uint32_t getCRC(size_t idx) const;
void setCRC(uint32_t _crc);
void setCRC(uint32_t _crc, size_t idx);
std::string getSessId() const;
std::string getSessId(size_t index) const;
private:
Util::FieldAccX sync;
Util::FieldAccX now;
Util::FieldAccX time;
Util::FieldAccX lastSecond;
Util::FieldAccX down;
Util::FieldAccX up;
Util::FieldAccX host;
Util::FieldAccX stream;
Util::FieldAccX connector;
Util::FieldAccX crc;
};
class Users : public Comms{
public:
Users();
Users(const Users &rhs);
void reload(const std::string &_streamName = "", bool _master = false, bool reIssue = false);
void reload(const std::string &_streamName, size_t track, uint8_t initialState = 0x00);
operator bool() const{return dataPage.mapped;}
uint32_t getTrack() const;
uint32_t getTrack(size_t idx) const;
void setTrack(uint32_t _track);
void setTrack(uint32_t _track, size_t idx);
size_t getKeyNum() const;
size_t getKeyNum(size_t idx) const;
void setKeyNum(size_t _keyNum);
void setKeyNum(size_t _keyNum, size_t idx);
private:
std::string streamName;
Util::FieldAccX track;
Util::FieldAccX keyNum;
};
}// namespace Comms

View file

@ -39,6 +39,7 @@ bool Util::Config::is_restarting = false;
static Socket::Server *serv_sock_pointer = 0; static Socket::Server *serv_sock_pointer = 0;
uint32_t Util::Config::printDebugLevel = DEBUG; // uint32_t Util::Config::printDebugLevel = DEBUG; //
std::string Util::Config::streamName; std::string Util::Config::streamName;
std::string Util::Config::exitReason;
std::string Util::listenInterface; std::string Util::listenInterface;
uint32_t Util::listenPort = 0; uint32_t Util::listenPort = 0;
@ -427,6 +428,7 @@ void Util::Config::activate(){
sigaction(SIGHUP, &new_action, NULL); sigaction(SIGHUP, &new_action, NULL);
sigaction(SIGTERM, &new_action, NULL); sigaction(SIGTERM, &new_action, NULL);
sigaction(SIGPIPE, &new_action, NULL); sigaction(SIGPIPE, &new_action, NULL);
sigaction(SIGFPE, &new_action, NULL);
// check if a child signal handler isn't set already, if so, set it. // check if a child signal handler isn't set already, if so, set it.
sigaction(SIGCHLD, 0, &cur_action); sigaction(SIGCHLD, 0, &cur_action);
if (cur_action.sa_handler == SIG_DFL || cur_action.sa_handler == SIG_IGN){ if (cur_action.sa_handler == SIG_DFL || cur_action.sa_handler == SIG_IGN){
@ -448,6 +450,7 @@ void Util::Config::signal_handler(int signum, siginfo_t *sigInfo, void *ignore){
static int ctr = 0; static int ctr = 0;
if (!is_active && ++ctr > 4){BACKTRACE;} if (!is_active && ++ctr > 4){BACKTRACE;}
#endif #endif
logExitReason("Setting is_active to false due to received signal interrupt");
is_active = false; is_active = false;
default: default:
switch (sigInfo->si_code){ switch (sigInfo->si_code){
@ -475,6 +478,7 @@ void Util::Config::signal_handler(int signum, siginfo_t *sigInfo, void *ignore){
// We ignore SIGPIPE to prevent messages triggering another SIGPIPE. // We ignore SIGPIPE to prevent messages triggering another SIGPIPE.
// Loops are bad, m'kay? // Loops are bad, m'kay?
break; break;
case SIGFPE: break;
} }
}// signal_handler }// signal_handler

View file

@ -27,6 +27,10 @@ namespace Util{
static bool is_restarting; ///< Set to true when restarting, set to false on boot. static bool is_restarting; ///< Set to true when restarting, set to false on boot.
static uint32_t printDebugLevel; static uint32_t printDebugLevel;
static std::string streamName; ///< Used by debug messages to identify the stream name static std::string streamName; ///< Used by debug messages to identify the stream name
static std::string exitReason;
static void logExitReason(const std::string &reason){
if (!exitReason.size()){exitReason = reason;}
}
// functions // functions
Config(); Config();
Config(std::string cmd); Config(std::string cmd);

View file

@ -110,12 +110,13 @@ static inline void show_stackframe(){}
#endif #endif
#ifndef SHM_DATASIZE #define DTSH_FRAGMENT_SIZE 13
#define SHM_DATASIZE 20 #define DTSH_KEY_SIZE 25
#endif #define DTSH_PART_SIZE 9
#define AUDIO_KEY_INTERVAL \ #ifndef SHM_DATASIZE
2000 ///< This define controls the keyframe interval for non-video tracks, such as audio and metadata tracks. #define SHM_DATASIZE 40
#endif
#ifndef STATS_DELAY #ifndef STATS_DELAY
#define STATS_DELAY 15 #define STATS_DELAY 15
@ -143,10 +144,62 @@ static inline void show_stackframe(){}
/// Does not affect live streams. /// Does not affect live streams.
#define FLIP_MIN_DURATION 20000 #define FLIP_MIN_DURATION 20000
/// Interval where the input refreshes the user data for stats etc. // New meta
#define SHM_STREAM_META "MstMeta%s" //%s stream name
#define SHM_STREAM_META_LEN 8 * 1024 * 1024
#define SHM_STREAM_META_ITEM 2 * 1024
#define SHM_STREAM_TM "MstTrak%s@%" PRIu32 "-%zu" //%s stream name
#define SHM_STREAM_TRACK_ITEM 16 * 1024 * 1024
#define SHM_STREAM_TRACK_LEN 4 * SHM_STREAM_TRACK_ITEM
// Default values, these will scale up and down when needed, and are mainly used as starting values.
#define DEFAULT_TRACK_COUNT 100
#define DEFAULT_FRAGMENT_COUNT 2000
#define DEFAULT_KEY_COUNT \
3 * DEFAULT_FRAGMENT_COUNT // A highest average of 5 keys / fragment is assumed
#define DEFAULT_PART_COUNT \
400 * DEFAULT_KEY_COUNT // A highest average of 500 parts / key is
// assumed
#define DEFAULT_PAGE_COUNT DEFAULT_KEY_COUNT // Assume every page is a key to ensure enough space
#define DEFAULT_FRAGMENT_DURATION 5000
#define META_META_OFFSET 104
#define META_META_RECORDSIZE 576
#define META_TRACK_OFFSET 148
#define META_TRACK_RECORDSIZE 1893
#define TRACK_TRACK_OFFSET 184
#define TRACK_TRACK_RECORDSIZE 362 + (1 * 1024 * 1024)
#define TRACK_FRAGMENT_OFFSET 68
#define TRACK_FRAGMENT_RECORDSIZE 14
#define TRACK_KEY_OFFSET 90
#define TRACK_KEY_RECORDSIZE 42
#define TRACK_PART_OFFSET 60
#define TRACK_PART_RECORDSIZE 8
#define TRACK_PAGE_OFFSET 92
#define TRACK_PAGE_RECORDSIZE 36
#define COMMS_STATISTICS "MstStat"
#define COMMS_STATISTICS_INITSIZE 8 * 1024 * 1024
#define COMMS_USERS "MstUser%s" //%s stream name
#define COMMS_USERS_INITSIZE 8 * 1024 * 1024
#define SEM_STATISTICS "/MstStat"
#define SEM_USERS "/MstUser%s" //%s stream name
#define SHM_TRACK_DATA "MstData%s@%zu_%zu" //%s stream name, %zu track ID, %PRIu32 page #
// End new meta
#define INPUT_USER_INTERVAL 1000 #define INPUT_USER_INTERVAL 1000
#define SHM_STREAM_INDEX "MstSTRM%s" //%s stream name
#define SHM_STREAM_STATE "MstSTATE%s" //%s stream name #define SHM_STREAM_STATE "MstSTATE%s" //%s stream name
#define SHM_STREAM_CONF "MstSCnf%s" //%s stream name #define SHM_STREAM_CONF "MstSCnf%s" //%s stream name
#define SHM_GLOBAL_CONF "MstGlobalConfig" #define SHM_GLOBAL_CONF "MstGlobalConfig"
@ -157,12 +210,7 @@ static inline void show_stackframe(){}
#define STRMSTAT_READY 4 #define STRMSTAT_READY 4
#define STRMSTAT_SHUTDOWN 5 #define STRMSTAT_SHUTDOWN 5
#define STRMSTAT_INVALID 255 #define STRMSTAT_INVALID 255
#define SHM_TRACK_META "MstTRAK%s@%lu" //%s stream name, %lu track ID
#define SHM_TRACK_INDEX "MstTRID%s@%lu" //%s stream name, %lu track ID
#define SHM_TRACK_INDEX_SIZE 8192
#define SHM_TRACK_DATA "MstDATA%s@%lu_%lu" //%s stream name, %lu track ID, %lu page #
#define SHM_STATISTICS "MstSTAT"
#define SHM_USERS "MstUSER%s" //%s stream name
#define SHM_TRIGGER "MstTRGR%s" //%s trigger name #define SHM_TRIGGER "MstTRGR%s" //%s trigger name
#define SEM_LIVE "/MstLIVE%s" //%s stream name #define SEM_LIVE "/MstLIVE%s" //%s stream name
#define SEM_INPUT "/MstInpt%s" //%s stream name #define SEM_INPUT "/MstInpt%s" //%s stream name
@ -180,7 +228,7 @@ static inline void show_stackframe(){}
#define SHM_STREAM_ENCRYPT "MstCRYP%s" //%s stream name #define SHM_STREAM_ENCRYPT "MstCRYP%s" //%s stream name
#define SIMUL_TRACKS 20 #define SIMUL_TRACKS 40
#ifndef UDP_API_HOST #ifndef UDP_API_HOST
#define UDP_API_HOST "localhost" #define UDP_API_HOST "localhost"
@ -190,8 +238,20 @@ static inline void show_stackframe(){}
#define UDP_API_PORT 4242 #define UDP_API_PORT 4242
#endif #endif
#define INVALID_TRACK_ID 0
// The amount of milliseconds a simulated live stream is allowed to be "behind". // The amount of milliseconds a simulated live stream is allowed to be "behind".
// Setting this value to lower than 2 seconds **WILL** cause stuttering in playback due to buffer negotiation. // Setting this value to lower than 2 seconds **WILL** cause stuttering in playback due to buffer negotiation.
#define SIMULATED_LIVE_BUFFER 7000 #define SIMULATED_LIVE_BUFFER 7000
#define STAT_EX_SIZE 177
#define PLAY_EX_SIZE 2 + 6 * SIMUL_TRACKS
#define INVALID_TRACK_ID 0xFFFFFFFF
#define INVALID_KEY_NUM 0xFFFFFFFF
#define INVALID_PAGE_NUM 0xFFFF
#define INVALID_RECORD_INDEX 0xFFFFFFFFFFFFFFFF
#define MAX_SIZE_T 0xFFFFFFFF
#define NEW_TRACK_ID 0x80000000
#define QUICK_NEGOTIATE 0xC0000000

View file

@ -16,11 +16,10 @@ static int on_mbedtls_wants_to_read(void *user, unsigned char *buf,
size_t len); /* Called when mbedtls wants to read data from e.g. a socket. */ size_t len); /* Called when mbedtls wants to read data from e.g. a socket. */
static int on_mbedtls_wants_to_write(void *user, const unsigned char *buf, static int on_mbedtls_wants_to_write(void *user, const unsigned char *buf,
size_t len); /* Called when mbedtls wants to write data to e.g. a socket. */ size_t len); /* Called when mbedtls wants to write data to e.g. a socket. */
static std::string mbedtls_err_to_string(int r);
/* ----------------------------------------- */ /* ----------------------------------------- */
DTLSSRTPHandshake::DTLSSRTPHandshake() : write_callback(NULL), cert(NULL), key(NULL){ DTLSSRTPHandshake::DTLSSRTPHandshake() : cert(NULL), key(NULL), write_callback(NULL){
memset((void *)&entropy_ctx, 0x00, sizeof(entropy_ctx)); memset((void *)&entropy_ctx, 0x00, sizeof(entropy_ctx));
memset((void *)&rand_ctx, 0x00, sizeof(rand_ctx)); memset((void *)&rand_ctx, 0x00, sizeof(rand_ctx));
memset((void *)&ssl_ctx, 0x00, sizeof(ssl_ctx)); memset((void *)&ssl_ctx, 0x00, sizeof(ssl_ctx));
@ -381,7 +380,7 @@ static void print_mbedtls_error(int r){
} }
static void print_mbedtls_debug_message(void *ctx, int level, const char *file, int line, const char *str){ static void print_mbedtls_debug_message(void *ctx, int level, const char *file, int line, const char *str){
DONTEVEN_MSG("%s:%04d: %.*s", file, line, strlen(str) - 1, str); DONTEVEN_MSG("%s:%04d: %.*s", file, line, (int)strlen(str) - 1, str);
#if LOG_TO_FILE #if LOG_TO_FILE
static std::ofstream ofs; static std::ofstream ofs;
@ -392,19 +391,4 @@ static void print_mbedtls_debug_message(void *ctx, int level, const char *file,
#endif #endif
} }
static std::string mbedtls_err_to_string(int r){
switch (r){
case MBEDTLS_ERR_SSL_WANT_READ:{
return "MBEDTLS_ERR_SSL_WANT_READ";
}
case MBEDTLS_ERR_SSL_WANT_WRITE:{
return "MBEDTLS_ERR_SSL_WANT_WRITE";
}
default:{
print_mbedtls_error(r);
return "UNKNOWN";
}
}
}
/* ---------------------------------------- */ /* ---------------------------------------- */

View file

@ -18,8 +18,11 @@
class DTLSSRTPHandshake{ class DTLSSRTPHandshake{
public: public:
DTLSSRTPHandshake(); DTLSSRTPHandshake();
int init(mbedtls_x509_crt *certificate, int init(mbedtls_x509_crt *certificate, mbedtls_pk_context *privateKey,
mbedtls_pk_context *privateKey, int (*writeCallback)(const uint8_t *data, int *nbytes)); // writeCallback should return 0 on succes < 0 on error. nbytes holds the number of bytes to be sent and needs to be set to the number of bytes actually sent. int (*writeCallback)(const uint8_t *data,
int *nbytes)); // writeCallback should return 0 on succes < 0 on error.
// nbytes holds the number of bytes to be sent and needs
// to be set to the number of bytes actually sent.
int shutdown(); int shutdown();
int parse(const uint8_t *data, size_t nbytes); int parse(const uint8_t *data, size_t nbytes);
bool hasKeyingMaterial(); bool hasKeyingMaterial();
@ -40,8 +43,9 @@ private:
public: public:
int (*write_callback)(const uint8_t *data, int *nbytes); int (*write_callback)(const uint8_t *data, int *nbytes);
std::deque<uint8_t> buffer; /* Accessed from BIO callbback. We copy the bytes you pass into `parse()` into this temporary buffer which is read by a trigger to `mbedlts_ssl_handshake()`. */ std::deque<uint8_t> buffer; /* Accessed from BIO callbback. We copy the bytes you pass into `parse()` into this
std::string cipher; /* selected SRTP cipher. */ temporary buffer which is read by a trigger to `mbedlts_ssl_handshake()`. */
std::string cipher; /* selected SRTP cipher. */
std::string remote_key; std::string remote_key;
std::string remote_salt; std::string remote_salt;
std::string local_key; std::string local_key;

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,12 @@
/// Holds all headers for DDVTECH Stream Container parsing/generation. /// Holds all headers for DDVTECH Stream Container parsing/generation.
#pragma once #pragma once
#include "defines.h"
#include "json.h" #include "json.h"
#include "shared_memory.h"
#include "socket.h" #include "socket.h"
#include "timing.h" #include "timing.h"
#include "util.h"
#include <deque> #include <deque>
#include <iostream> #include <iostream>
#include <set> #include <set>
@ -28,6 +31,8 @@
namespace DTSC{ namespace DTSC{
extern uint64_t veryUglyJitterOverride;
///\brief This enum holds all possible datatypes for DTSC packets. ///\brief This enum holds all possible datatypes for DTSC packets.
enum datatype{ enum datatype{
AUDIO, ///< Stream Audio data AUDIO, ///< Stream Audio data
@ -43,26 +48,6 @@ namespace DTSC{
extern char Magic_Packet2[]; ///< The magic bytes for a DTSC packet version 2 extern char Magic_Packet2[]; ///< The magic bytes for a DTSC packet version 2
extern char Magic_Command[]; ///< The magic bytes for a DTCM packet extern char Magic_Command[]; ///< The magic bytes for a DTCM packet
///\brief A simple structure used for ordering byte seek positions.
struct seekPos{
///\brief Less-than comparison for seekPos structures.
///\param rhs The seekPos to compare with.
///\return Whether this object is smaller than rhs.
bool operator<(const seekPos &rhs) const{
if (seekTime < rhs.seekTime){
return true;
}else{
if (seekTime == rhs.seekTime){
if (trackID < rhs.trackID){return true;}
}
}
return false;
}
long long unsigned int seekTime; ///< Stores the timestamp of the DTSC packet referenced by this structure.
long long unsigned int bytePos; ///< Stores the byteposition of the DTSC packet referenced by this structure.
unsigned int trackID; ///< Stores the track the DTSC packet referenced by this structure is associated with.
};
enum packType{DTSC_INVALID, DTSC_HEAD, DTSC_V1, DTSC_V2, DTCM}; enum packType{DTSC_INVALID, DTSC_HEAD, DTSC_V1, DTSC_V2, DTCM};
/// This class allows scanning through raw binary format DTSC data. /// This class allows scanning through raw binary format DTSC data.
@ -78,8 +63,6 @@ namespace DTSC{
Scan getMember(const std::string &indice) const; Scan getMember(const std::string &indice) const;
Scan getMember(const char *indice) const; Scan getMember(const char *indice) const;
Scan getMember(const char *indice, size_t ind_len) const; Scan getMember(const char *indice, size_t ind_len) const;
void nullMember(const std::string &indice);
void nullMember(const char *indice, size_t ind_len);
Scan getIndice(size_t num) const; Scan getIndice(size_t num) const;
std::string getIndiceName(size_t num) const; std::string getIndiceName(size_t num) const;
size_t getSize() const; size_t getSize() const;
@ -104,7 +87,7 @@ namespace DTSC{
class Packet{ class Packet{
public: public:
Packet(); Packet();
Packet(const Packet &rhs); Packet(const Packet &rhs, size_t idx = INVALID_TRACK_ID);
Packet(const char *data_, unsigned int len, bool noCopy = false); Packet(const char *data_, unsigned int len, bool noCopy = false);
virtual ~Packet(); virtual ~Packet();
void null(); void null();
@ -113,9 +96,8 @@ namespace DTSC{
packType getVersion() const; packType getVersion() const;
void reInit(Socket::Connection &src); void reInit(Socket::Connection &src);
void reInit(const char *data_, unsigned int len, bool noCopy = false); void reInit(const char *data_, unsigned int len, bool noCopy = false);
void genericFill(long long packTime, long long packOffset, long long packTrack, void genericFill(uint64_t packTime, int64_t packOffset, uint32_t packTrack, const char *packData,
const char *packData, long long packDataSize, uint64_t packBytePos, size_t packDataSize, uint64_t packBytePos, bool isKeyframe);
bool isKeyframe, int64_t bootMsOffset = 0);
void appendData(const char *appendData, uint32_t appendLen); void appendData(const char *appendData, uint32_t appendLen);
void getString(const char *identifier, char *&result, size_t &len) const; void getString(const char *identifier, char *&result, size_t &len) const;
void getString(const char *identifier, std::string &result) const; void getString(const char *identifier, std::string &result) const;
@ -129,7 +111,6 @@ namespace DTSC{
void setKeyFrame(bool kf); void setKeyFrame(bool kf);
virtual uint64_t getTime() const; virtual uint64_t getTime() const;
void setTime(uint64_t _time); void setTime(uint64_t _time);
void nullMember(const std::string &memb);
size_t getTrackId() const; size_t getTrackId() const;
char *getData() const; char *getData() const;
size_t getDataLen() const; size_t getDataLen() const;
@ -139,7 +120,6 @@ namespace DTSC{
JSON::Value toJSON() const; JSON::Value toJSON() const;
std::string toSummary() const; std::string toSummary() const;
Scan getScan() const; Scan getScan() const;
Scan getScan();
protected: protected:
bool master; bool master;
@ -161,315 +141,358 @@ namespace DTSC{
: Packet(data_, len, noCopy){ : Packet(data_, len, noCopy){
timeOverride = reTime; timeOverride = reTime;
} }
~RetimedPacket(){}
virtual uint64_t getTime() const{return timeOverride;} virtual uint64_t getTime() const{return timeOverride;}
protected: protected:
uint64_t timeOverride; uint64_t timeOverride;
}; };
/// A simple structure used for ordering byte seek positions. class Parts{
struct livePos{
livePos(){
seekTime = 0;
trackID = 0;
}
livePos(const livePos &rhs){
seekTime = rhs.seekTime;
trackID = rhs.trackID;
}
void operator=(const livePos &rhs){
seekTime = rhs.seekTime;
trackID = rhs.trackID;
}
bool operator==(const livePos &rhs){
return seekTime == rhs.seekTime && trackID == rhs.trackID;
}
bool operator!=(const livePos &rhs){
return seekTime != rhs.seekTime || trackID != rhs.trackID;
}
bool operator<(const livePos &rhs) const{
if (seekTime < rhs.seekTime){
return true;
}else{
if (seekTime > rhs.seekTime){return false;}
}
return (trackID < rhs.trackID);
}
long long unsigned int seekTime;
unsigned int trackID;
};
/*LTS-START*/
///\brief Basic class supporting initialization Vectors.
///
/// These are used for encryption of data.
class Ivec{
public: public:
Ivec(); Parts(const Util::RelAccX &_parts);
Ivec(long long int iVec); size_t getFirstValid() const;
void setIvec(long long int iVec); size_t getEndValid() const;
void setIvec(std::string iVec); size_t getValidCount() const;
void setIvec(const char *iVec, int len); size_t getSize(size_t idx) const;
long long int asInt(); uint64_t getDuration(size_t idx) const;
char *getData(); int64_t getOffset(size_t idx) const;
private: private:
///\brief Data storage for this initialization vector. const Util::RelAccX &parts;
/// Util::RelAccXFieldData sizeField;
/// - 8 bytes: MSB storage of the initialization vector. Util::RelAccXFieldData durationField;
char data[8]; Util::RelAccXFieldData offsetField;
}; };
/*LTS-END*/
///\brief Basic class for storage of data associated with single DTSC packets, a.k.a. parts. class Keys{
class Part{
public: public:
uint32_t getSize(); Keys(Util::RelAccX &_keys);
void setSize(uint32_t newSize); Keys(const Util::RelAccX &_keys);
uint32_t getDuration(); size_t getFirstValid() const;
void setDuration(uint32_t newDuration); size_t getEndValid() const;
uint32_t getOffset(); size_t getValidCount() const;
void setOffset(uint32_t newOffset); size_t getFirstPart(size_t idx) const;
char *getData(); size_t getBpos(size_t idx) const;
void toPrettyString(std::ostream &str, int indent = 0); uint64_t getDuration(size_t idx) const;
size_t getNumber(size_t idx) const;
size_t getParts(size_t idx) const;
uint64_t getTime(size_t idx) const;
void setSize(size_t idx, size_t _size);
size_t getSize(size_t idx) const;
size_t getNumForTime(uint64_t time) const;
private: private:
#define PACKED_PART_SIZE 9 bool isConst;
///\brief Data storage for this Part. Util::RelAccX empty;
///
/// - 3 bytes: MSB storage of the payload size of this packet in bytes. Util::RelAccX &keys;
/// - 3 bytes: MSB storage of the duration of this packet in milliseconds. const Util::RelAccX &cKeys;
/// - 3 bytes: MSB storage of the presentation time offset of this packet in milliseconds.
char data[PACKED_PART_SIZE]; Util::RelAccXFieldData firstPartField;
Util::RelAccXFieldData bposField;
Util::RelAccXFieldData durationField;
Util::RelAccXFieldData numberField;
Util::RelAccXFieldData partsField;
Util::RelAccXFieldData timeField;
Util::RelAccXFieldData sizeField;
}; };
///\brief Basic class for storage of data associated with keyframes. class Fragments{
///
/// When deleting this object, make sure to remove all DTSC::Part associated with it, if any. If you fail doing this, it *will* cause data corruption.
class Key{
public: public:
unsigned long long getBpos(); Fragments(const Util::RelAccX &_fragments);
void setBpos(unsigned long long newBpos); size_t getFirstValid() const;
unsigned long getLength(); size_t getEndValid() const;
void setLength(unsigned long newLength); size_t getValidCount() const;
unsigned long getNumber(); uint64_t getDuration(size_t idx) const;
void setNumber(unsigned long newNumber); size_t getKeycount(size_t idx) const;
unsigned short getParts(); size_t getFirstKey(size_t idx) const;
void setParts(unsigned short newParts); size_t getSize(size_t idx) const;
unsigned long long getTime();
void setTime(unsigned long long newTime);
char *getData();
void toPrettyString(std::ostream &str, int indent = 0);
private: private:
#define PACKED_KEY_SIZE 25 const Util::RelAccX &fragments;
///\brief Data storage for this Key.
///
/// - 8 bytes: MSB storage of the position of the first packet of this keyframe within the file.
/// - 3 bytes: MSB storage of the duration of this keyframe.
/// - 4 bytes: MSB storage of the number of this keyframe.
/// - 2 bytes: MSB storage of the amount of parts in this keyframe.
/// - 8 bytes: MSB storage of the timestamp associated with this keyframe's first packet.
char data[PACKED_KEY_SIZE];
}; };
///\brief Basic class for storage of data associated with fragments.
class Fragment{
public:
unsigned long getDuration();
void setDuration(unsigned long newDuration);
char getLength();
void setLength(char newLength);
unsigned long getNumber();
void setNumber(unsigned long newNumber);
unsigned long getSize();
void setSize(unsigned long newSize);
char *getData();
void toPrettyString(std::ostream &str, int indent = 0);
private:
#define PACKED_FRAGMENT_SIZE 13
///\brief Data storage for this Fragment.
///
/// - 4 bytes: duration (in milliseconds)
/// - 1 byte: length (amount of keyframes)
/// - 4 bytes: number of first keyframe in fragment
/// - 4 bytes: size of fragment in bytes
char data[PACKED_FRAGMENT_SIZE];
};
///\brief Class for storage of track data
class Track{ class Track{
public: public:
Track(); Util::RelAccX parts;
Track(JSON::Value &trackRef); Util::RelAccX keys;
Track(Scan &trackRef); Util::RelAccX fragments;
void clearParts();
inline operator bool() const{ Util::RelAccX pages;
return (parts.size() && keySizes.size() && (keySizes.size() == keys.size()));
}
/*
void update(long long packTime, long long packOffset, long long packDataSize, uint64_t
packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size = 1900);
*/
void update(long long packTime, long long packOffset, long long packDataSize,
uint64_t packBytePos, bool isKeyframe, long long packSendSize,
unsigned long segment_size = 1900, const char *iVec = 0);
int getSendLen(bool skipDynamic = false);
void send(Socket::Connection &conn, bool skipDynamic = false);
void writeTo(char *&p);
JSON::Value toJSON(bool skipDynamic = false);
std::deque<Fragment> fragments;
std::deque<Key> keys;
std::deque<unsigned long> keySizes;
std::deque<Part> parts;
std::deque<Ivec> ivecs; /*LTS*/
Key &getKey(unsigned int keyNum);
Fragment &getFrag(unsigned int fragNum);
unsigned int timeToKeynum(unsigned int timestamp);
uint32_t timeToFragnum(uint64_t timestamp);
void reset();
void toPrettyString(std::ostream &str, int indent = 0, int verbosity = 0);
void finalize();
uint32_t biggestFragment();
std::string getIdentifier(); Util::RelAccX track;
std::string getWritableIdentifier();
unsigned int trackID;
uint64_t firstms;
uint64_t lastms;
int bps;
int max_bps;
int missedFrags;
std::string init;
std::string codec;
std::string type;
std::string lang; ///< ISO 639-2 Language of track, empty or und if unknown.
uint32_t minKeepAway; ///< Time in MS to never seek closer than live point to
// audio only
int rate;
int size;
int channels;
// video only
int width;
int height;
int fpks;
void removeFirstKey();
uint32_t secsSinceFirstFragmentInsert();
private: // Internal buffers so we don't always need to search for everything
std::string cachedIdent; Util::RelAccXFieldData trackIdField;
std::deque<uint32_t> fragInsertTime; Util::RelAccXFieldData trackTypeField;
Util::RelAccXFieldData trackCodecField;
Util::RelAccXFieldData trackFirstmsField;
Util::RelAccXFieldData trackLastmsField;
Util::RelAccXFieldData trackBpsField;
Util::RelAccXFieldData trackMaxbpsField;
Util::RelAccXFieldData trackLangField;
Util::RelAccXFieldData trackInitField;
Util::RelAccXFieldData trackRateField;
Util::RelAccXFieldData trackSizeField;
Util::RelAccXFieldData trackChannelsField;
Util::RelAccXFieldData trackWidthField;
Util::RelAccXFieldData trackHeightField;
Util::RelAccXFieldData trackFpksField;
Util::RelAccXFieldData trackMissedFragsField;
Util::RelAccXFieldData partSizeField;
Util::RelAccXFieldData partDurationField;
Util::RelAccXFieldData partOffsetField;
Util::RelAccXFieldData keyFirstPartField;
Util::RelAccXFieldData keyBposField;
Util::RelAccXFieldData keyDurationField;
Util::RelAccXFieldData keyNumberField;
Util::RelAccXFieldData keyPartsField;
Util::RelAccXFieldData keyTimeField;
Util::RelAccXFieldData keySizeField;
Util::RelAccXFieldData fragmentDurationField;
Util::RelAccXFieldData fragmentKeysField;
Util::RelAccXFieldData fragmentFirstKeyField;
Util::RelAccXFieldData fragmentSizeField;
}; };
///\brief Class for storage of meta data
class Meta{ class Meta{
/// \todo Make toJSON().toNetpacked() shorter
public: public:
Meta(); Meta(const std::string &_streamName, const DTSC::Scan &src);
Meta(const DTSC::Packet &source); Meta(const std::string &_streamName = "", bool master = true);
Meta(JSON::Value &meta); Meta(const std::string &_streamName, const std::string &fileName);
bool nextIsKey;
inline operator bool() const{// returns if the object contains valid meta data BY LOOKING AT vod/live FLAGS ~Meta();
return vod || live; void reInit(const std::string &_streamName, bool master = true);
} void reInit(const std::string &_streamName, const std::string &fileName);
void reinit(const DTSC::Packet &source); void reInit(const std::string &_streamName, const DTSC::Scan &src);
void update(const DTSC::Packet &pack, unsigned long segment_size = 1900);
void updatePosOverride(DTSC::Packet &pack, uint64_t bpos);
void update(JSON::Value &pack, unsigned long segment_size = 1900);
/*LTS
void update(long long packTime, long long packOffset, long long packTrack, long long
packDataSize, uint64_t packBytePos, bool isKeyframe, long long packSendSize = 0, unsigned long
segment_size = 1900); LTS*/
void update(long long packTime, long long packOffset, long long packTrack,
long long packDataSize, uint64_t packBytePos, bool isKeyframe,
long long packSendSize = 0, unsigned long segment_size = 1900, const char *iVec = 0);
unsigned int getSendLen(bool skipDynamic = false,
std::set<unsigned long> selectedTracks = std::set<unsigned long>());
void send(Socket::Connection &conn, bool skipDynamic = false,
std::set<unsigned long> selectedTracks = std::set<unsigned long>());
void writeTo(char *p);
JSON::Value toJSON();
void reset();
bool toFile(const std::string &fileName);
void toPrettyString(std::ostream &str, int indent = 0, int verbosity = 0);
// members:
std::map<unsigned int, Track> tracks;
Track &mainTrack();
uint32_t biggestFragment();
bool vod;
bool live;
bool merged;
uint16_t version;
int64_t moreheader;
int64_t bufferWindow;
int64_t bootMsOffset; ///< Millis to add to packet timestamps to get millis since system boot.
std::string sourceURI;
JSON::Value inputLocalVars;
};
/// An iterator helper for easily iterating over the parts in a Fragment. void refresh();
class PartIter{
public:
PartIter(Track &Trk, Fragment &frag);
Part &operator*() const; ///< Dereferences into a Value reference.
Part *operator->() const; ///< Dereferences into a Value reference.
operator bool() const; ///< True if not done iterating.
PartIter &operator++(); ///< Go to next iteration.
private:
uint32_t lastKey;
uint32_t currInKey;
Track *tRef;
std::deque<Part>::iterator pIt;
std::deque<Key>::iterator kIt;
};
/// A simple wrapper class that will open a file and allow easy reading/writing of DTSC data from/to it.
class File{
public:
File();
File(const File &rhs);
File(std::string filename, bool create = false);
File &operator=(const File &rhs);
operator bool() const; operator bool() const;
~File();
Meta &getMeta(); void setMaster(bool _master);
long long int getLastReadPos(); bool getMaster() const;
bool writeHeader(std::string &header, bool force = false);
long long int addHeader(std::string &header); void clear();
long int getBytePosEOF();
long int getBytePos(); void minimalFrom(const Meta &src);
bool reachedEOF();
void seekNext(); bool trackLoaded(size_t idx) const;
void parseNext(); bool trackValid(size_t idx) const;
DTSC::Packet &getPacket(); size_t trackCount() const;
bool seek_time(unsigned int ms);
bool seek_time(unsigned int ms, unsigned int trackNo, bool forceSeek = false); size_t addCopy(size_t source);
bool seek_bpos(int bpos); size_t addDelayedTrack(size_t fragCount = DEFAULT_FRAGMENT_COUNT, size_t keyCount = DEFAULT_KEY_COUNT,
void rewritePacket(std::string &newPacket, int bytePos); size_t partCount = DEFAULT_PART_COUNT, size_t pageCount = DEFAULT_PAGE_COUNT);
void writePacket(std::string &newPacket); size_t addTrack(size_t fragCount = DEFAULT_FRAGMENT_COUNT, size_t keyCount = DEFAULT_KEY_COUNT,
void writePacket(JSON::Value &newPacket); size_t partCount = DEFAULT_PART_COUNT, size_t pageCount = DEFAULT_PAGE_COUNT,
bool atKeyframe(); bool setValid = true);
void selectTracks(std::set<unsigned long> &tracks); void resizeTrack(size_t source, size_t fragCount = DEFAULT_FRAGMENT_COUNT, size_t keyCount = DEFAULT_KEY_COUNT,
size_t partCount = DEFAULT_PART_COUNT, size_t pageCount = DEFAULT_PAGE_COUNT);
void initializeTrack(Track &t, size_t fragCount = DEFAULT_FRAGMENT_COUNT, size_t keyCount = DEFAULT_KEY_COUNT,
size_t parCount = DEFAULT_PART_COUNT, size_t pageCount = DEFAULT_PAGE_COUNT);
void merge(const DTSC::Meta &M, bool deleteTracks = true, bool copyData = true);
void updatePosOverride(DTSC::Packet &pack, uint64_t bpos);
void update(const DTSC::Packet &pack);
void update(uint64_t packTime, int64_t packOffset, uint32_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, uint64_t packSendSize = 0);
size_t trackIDToIndex(size_t trackID, size_t pid = 0) const;
std::string getTrackIdentifier(size_t idx, bool unique = false) const;
void setInit(size_t trackIdx, const std::string &init);
void setInit(size_t trackIdx, const char *init, size_t initLen);
std::string getInit(size_t idx) const;
void setSource(const std::string &src);
std::string getSource() const;
void setID(size_t trackIdx, size_t id);
size_t getID(size_t trackIdx) const;
void markUpdated(size_t trackIdx);
uint64_t getLastUpdated(size_t trackIdx) const;
uint64_t getLastUpdated() const;
void setChannels(size_t trackIdx, uint16_t channels);
uint16_t getChannels(size_t trackIdx) const;
void setRate(size_t trackIdx, uint32_t rate);
uint32_t getRate(size_t trackIdx) const;
void setWidth(size_t trackIdx, uint32_t width);
uint32_t getWidth(size_t trackIdx) const;
void setHeight(size_t trackIdx, uint32_t height);
uint32_t getHeight(size_t trackIdx) const;
void setSize(size_t trackIdx, uint16_t size);
uint16_t getSize(size_t trackIdx) const;
void setType(size_t trackIdx, const std::string &type);
std::string getType(size_t trackIdx) const;
void setCodec(size_t trackIdx, const std::string &codec);
std::string getCodec(size_t trackIdx) const;
void setLang(size_t trackIdx, const std::string &lang);
std::string getLang(size_t trackIdx) const;
void setFirstms(size_t trackIdx, uint64_t firstms);
uint64_t getFirstms(size_t trackIdx) const;
void setLastms(size_t trackIdx, uint64_t lastms);
uint64_t getLastms(size_t trackIdx) const;
uint64_t getDuration(size_t trackIdx) const;
void setBps(size_t trackIdx, uint64_t bps);
uint64_t getBps(size_t trackIdx) const;
void setMaxBps(size_t trackIdx, uint64_t bps);
uint64_t getMaxBps(size_t trackIdx) const;
void setFpks(size_t trackIdx, uint64_t bps);
uint64_t getFpks(size_t trackIdx) const;
void setMissedFragments(size_t trackIdx, uint32_t missedFragments);
uint32_t getMissedFragments(size_t trackIdx) const;
void setMinKeepAway(size_t trackIdx, uint64_t minKeepAway);
uint64_t getMinKeepAway(size_t trackIdx) const;
/*LTS-START*/
void setSourceTrack(size_t trackIdx, size_t sourceTrack);
uint64_t getSourceTrack(size_t trackIdx) const;
void setEncryption(size_t trackIdx, const std::string &encryption);
std::string getEncryption(size_t trackIdx) const;
void setPlayReady(size_t trackIdx, const std::string &playReady);
std::string getPlayReady(size_t trackIdx) const;
void setWidevine(size_t trackIdx, const std::string &widevine);
std::string getWidevine(size_t trackIdx) const;
void setIvec(size_t trackIdx, uint64_t ivec);
uint64_t getIvec(size_t trackIdx) const;
void setMinimumFragmentDuration(uint64_t newFragmentDuration = DEFAULT_FRAGMENT_DURATION);
uint64_t getMinimumFragmentDuration() const;
/*LTS-END*/
/*LTS-START
uint64_t getFragmentDuration() const{return DEFAULT_FRAGMENT_DURATION;}
LTS-END*/
void setVod(bool vod = true);
bool getVod() const;
void setLive(bool live = true);
bool getLive() const;
bool hasBFrames(size_t idx = INVALID_TRACK_ID) const;
void setBufferWindow(uint64_t bufferWindow);
uint64_t getBufferWindow() const;
void setBootMsOffset(uint64_t bootMsOffset);
uint64_t getBootMsOffset() const;
std::set<size_t> getValidTracks(bool skipEmpty = false) const;
std::set<size_t> getMySourceTracks(size_t pid) const;
void validateTrack(size_t trackIdx);
void removeEmptyTracks();
void removeTrack(size_t trackIdx);
void removeFirstKey(size_t trackIdx);
size_t mainTrack() const;
uint32_t biggestFragment(uint32_t idx = INVALID_TRACK_ID) const;
bool tracksAlign(size_t idx1, size_t idx2) const;
uint64_t getTimeForFragmentIndex(uint32_t idx, uint32_t fragmentIdx) const;
uint32_t getFragmentIndexForTime(uint32_t idx, uint64_t timestamp) const;
uint64_t getTimeForKeyIndex(uint32_t idx, uint32_t keyIdx) const;
uint32_t getKeyIndexForTime(uint32_t idx, uint64_t timestamp) const;
uint32_t getPartIndex(const DTSC::Packet &pack, size_t idx) const;
bool nextPageAvailable(uint32_t idx, size_t currentPage) const;
size_t getPageNumberForTime(uint32_t idx, uint64_t time) const;
size_t getPageNumberForKey(uint32_t idx, uint64_t keynumber) const;
const Util::RelAccX &parts(size_t idx) const;
Util::RelAccX &keys(size_t idx);
const Util::RelAccX &keys(size_t idx) const;
const Util::RelAccX &fragments(size_t idx) const;
Util::RelAccX &pages(size_t idx);
const Util::RelAccX &pages(size_t idx) const;
std::string toPrettyString() const;
void remap(const std::string &_streamName = "");
uint64_t getSendLen(bool skipDynamic = false, std::set<size_t> selectedTracks = std::set<size_t>()) const;
void toFile(const std::string &fName) const;
void send(Socket::Connection &conn, bool skypDynamic = false,
std::set<size_t> selectedTracks = std::set<size_t>(), bool reID = false) const;
void toJSON(JSON::Value &res, bool skipDynamic = true, bool tracksOnly = false) const;
std::string getStreamName() const{return streamName;}
JSON::Value inputLocalVars;
uint8_t version;
protected:
void sBufMem(size_t trackCount = DEFAULT_TRACK_COUNT);
void sBufShm(const std::string &_streamName, size_t trackCount = DEFAULT_TRACK_COUNT, bool master = true);
void streamInit(size_t trackCount = DEFAULT_TRACK_COUNT);
std::string streamName;
IPC::sharedPage streamPage;
Util::RelAccX stream;
Util::RelAccX trackList;
std::map<size_t, Track> tracks;
std::map<size_t, IPC::sharedPage> tM;
bool isMaster;
char *streamMemBuf;
bool isMemBuf;
std::map<size_t, char *> tMemBuf;
std::map<size_t, size_t> sizeMemBuf;
private: private:
long int endPos; // Internal buffers so we don't always need to search for everything
void readHeader(int pos); Util::RelAccXFieldData streamVodField;
DTSC::Packet myPack; Util::RelAccXFieldData streamLiveField;
Meta metadata; Util::RelAccXFieldData streamSourceField;
std::map<unsigned int, std::string> trackMapping; Util::RelAccXFieldData streamBufferWindowField;
long long int currtime; Util::RelAccXFieldData streamBootMsOffsetField;
long long int lastreadpos; Util::RelAccXFieldData streamMinimumFragmentDurationField;
int currframe;
FILE *F;
unsigned long headerSize;
void *buffer;
bool created;
std::set<seekPos> currentPositions;
std::set<unsigned long> selectedTracks;
};
// FileWriter
Util::RelAccXFieldData trackValidField;
Util::RelAccXFieldData trackIdField;
Util::RelAccXFieldData trackTypeField;
Util::RelAccXFieldData trackCodecField;
Util::RelAccXFieldData trackPageField;
Util::RelAccXFieldData trackLastUpdateField;
Util::RelAccXFieldData trackPidField;
Util::RelAccXFieldData trackMinKeepAwayField;
Util::RelAccXFieldData trackSourceTidField;
Util::RelAccXFieldData trackEncryptionField;
Util::RelAccXFieldData trackIvecField;
Util::RelAccXFieldData trackWidevineField;
Util::RelAccXFieldData trackPlayreadyField;
};
}// namespace DTSC }// namespace DTSC

File diff suppressed because it is too large Load diff

View file

@ -1,225 +1,170 @@
#include "auth.h"
#include "bitfields.h" #include "bitfields.h"
#include "defines.h" #include "defines.h"
#include "encode.h"
#include "encryption.h" #include "encryption.h"
#include "http_parser.h" #include "h264.h"
#include "nal.h" /*LTS*/
#include "rijndael.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
namespace Encryption{ namespace Encryption{
/// helper function for printing binary values AES::AES(){mbedtls_aes_init(&ctx);}
std::string hexString(const char *data, unsigned long dataLen){
std::stringstream res; AES::~AES(){mbedtls_aes_free(&ctx);}
for (int i = 0; i < dataLen; i++){
res << std::hex << std::setw(2) << std::setfill('0') << (int)data[i]; void AES::setEncryptKey(const char *key){
if (i % 4 == 3){res << " ";} mbedtls_aes_setkey_enc(&ctx, (const unsigned char *)key, 128);
}
void AES::setDecryptKey(const char *key){
mbedtls_aes_setkey_dec(&ctx, (const unsigned char *)key, 128);
}
DTSC::Packet AES::encryptPacketCTR(const DTSC::Meta &M, const DTSC::Packet &src, uint64_t ivec, size_t newTrack){
DTSC::Packet res;
if (newTrack == INVALID_TRACK_ID){
FAIL_MSG("No target track given for track encryption!");
return res;
} }
return res.str();
}
std::string AES_Crypt(const std::string &data, const std::string &key, std::string &ivec){
return AES_Crypt(data.data(), data.size(), key.data(), ivec.data());
}
std::string AES_Crypt(const char *data, int dataLen, const char *key, const char *ivec){
char *outData = (char *)malloc(dataLen * sizeof(char));
memcpy(outData, data, dataLen);
AESFullCrypt(outData, dataLen, key, ivec);
std::string result = std::string(outData, dataLen);
free(outData);
return result;
}
/// This function encrypts data in-place.
/// It alters all parameters except dataLen.
/// Do not use it unless you know what you are doing.
void AESPartialCrypt(char *data, int dataLen, char *expandedKey, char *eCount, char *iVec,
unsigned int &num, bool &initialize){
if (initialize){
num = 0;
memset(eCount, 0, 16);
/// Before use, make sure the iVec is in the UPPER 8 bytes
memset(iVec + 8, 0, 8);
/// Before use, make sure this is not the only copy of the key you had. It is lost upon initialization
char cryptKey[224];
AES_set_encrypt_key(expandedKey, 128, cryptKey);
memcpy(expandedKey, cryptKey, 224);
initialize = false;
}
char *outData = (char *)malloc(dataLen * sizeof(char));
AES_CTR128_crypt(data, outData, dataLen, expandedKey, iVec, eCount, num);
memcpy(data, outData, dataLen);
free(outData);
}
// Generates the contentkey from a keyseed and a keyid
std::string PR_GenerateContentKey(std::string &keyseed, std::string &keyid){
char contentKey[16];
char dataBlob[92];
char keyA[32], keyB[32], keyC[32];
std::string keyidBytes = PR_GuidToByteArray(keyid);
memcpy(dataBlob, keyseed.data(), 30);
memcpy(dataBlob + 30, keyidBytes.data(), 16);
memcpy(dataBlob + 46, keyseed.data(), 30);
memcpy(dataBlob + 76, keyidBytes.data(), 16);
// KeyA is generated from keyseed/keyid
Secure::sha256bin(dataBlob, 46, keyA);
// KeyB is generated from keyseed/keyid/keyseed
Secure::sha256bin(dataBlob, 76, keyB);
// KeyC is generated from keyseed/keyid/keyseed/keyid
Secure::sha256bin(dataBlob, 92, keyC);
for (int i = 0; i < 16; i++){
contentKey[i] = keyA[i] ^ keyA[i + 16] ^ keyB[i] ^ keyB[i + 16] ^ keyC[i] ^ keyC[i + 16];
}
return std::string(contentKey, 16);
}
// Transforms a guid to the MS byte array representation
std::string PR_GuidToByteArray(std::string &guid){
char result[16];
result[0] = guid[3];
result[1] = guid[2];
result[2] = guid[1];
result[3] = guid[0];
result[4] = guid[5];
result[5] = guid[4];
result[6] = guid[7];
result[7] = guid[6];
memcpy(result + 8, guid.data() + 8, 8);
return std::string(result, 8);
}
/// This function encrypts data in-place.
void AESFullCrypt(char *data, int dataLen, const char *key, const char *ivec){
unsigned int num = 0;
char expandedKey[224];
memcpy(expandedKey, key, 16);
char eCount[16];
char iVec[16];
memcpy(iVec, ivec, 8);
bool initialize = true;
AESPartialCrypt(data, dataLen, expandedKey, eCount, iVec, num, initialize);
}
void encryptPlayReady(DTSC::Packet &thisPack, std::string &codec, const char *iVec, const char *key){
char *data; char *data;
size_t dataLen; size_t dataLen;
thisPack.getString("data", data, dataLen); src.getString("data", data, dataLen);
if (codec == "H264"){ size_t trackIdx = M.getSourceTrack(newTrack);
unsigned int num = 0;
char expandedKey[224];
memcpy(expandedKey, key, 16);
char eCount[16];
char initVec[16];
memcpy(initVec, iVec, 8);
bool initialize = true;
int pos = 0; char *encData = (char *)malloc(dataLen);
std::deque<int> nalSizes = nalu::parseNalSizes(thisPack); size_t dataOffset = 0;
for (std::deque<int>::iterator it = nalSizes.begin(); it != nalSizes.end(); it++){
int encrypted = (*it - 5) & ~0xF; // Bitmask to a multiple of 16 if (M.getType(trackIdx) == "video" && dataLen > 96){
int clear = *it - encrypted; dataOffset = dataLen - (int((dataLen - 96) / 16) * 16);
Encryption::AESPartialCrypt(data + pos + clear, encrypted, expandedKey, eCount, initVec, num, initialize); memcpy(encData, data, dataOffset);
pos += *it;
}
} }
if (codec == "AAC"){Encryption::AESFullCrypt(data, dataLen, key, iVec);}
}
/// Converts a hexidecimal string format key to binary string format. if (!encryptBlockCTR(ivec, data + dataOffset, encData + dataOffset, dataLen - dataOffset)){
std::string binKey(std::string hexkey){ FAIL_MSG("Failed to encrypt packet");
char newkey[16]; free(encData);
memset(newkey, 0, 16); return res;
for (size_t i = 0; i < hexkey.size(); ++i){
char c = hexkey[i];
newkey[i >> 1] |= ((c & 15) + (((c & 64) >> 6) | ((c & 64) >> 3))) << ((~i & 1) << 2);
} }
return std::string(newkey, 16);
}
/// Helper function for urlescape. res.genericFill(src.getTime(), src.getInt("offset"), newTrack, encData, dataLen, 0, src.getFlag("keyframe"));
/// Encodes a character as two hex digits. free(encData);
std::string hex(char dec){
char dig1 = (dec & 0xF0) >> 4;
char dig2 = (dec & 0x0F);
if (dig1 <= 9) dig1 += 48;
if (10 <= dig1 && dig1 <= 15) dig1 += 97 - 10;
if (dig2 <= 9) dig2 += 48;
if (10 <= dig2 && dig2 <= 15) dig2 += 97 - 10;
std::string r;
r.append(&dig1, 1);
r.append(&dig2, 1);
return r;
}
std::string hex(const std::string &input){
std::string res;
res.reserve(input.size() * 2);
for (unsigned int i = 0; i < input.size(); i++){res += hex(input[i]);}
return res; return res;
} }
void fillVerimatrix(verimatrixData &vmData){ std::string AES::encryptBlockCTR(uint64_t ivec, const std::string &inp){
int hostPos = vmData.url.find("://") + 3; char *resPtr = (char *)malloc(inp.size());
int portPos = vmData.url.find(":", hostPos); if (!encryptBlockCTR(ivec, inp.c_str(), resPtr, inp.size())){
free(resPtr);
std::string hostName = return "";
vmData.url.substr(hostPos, (portPos == std::string::npos ? portPos : portPos - hostPos)); }
int port = (portPos == std::string::npos ? 80 : atoi(vmData.url.data() + portPos + 1)); std::string result(resPtr, inp.size());
Socket::Connection veriConn(hostName, port, true); free(resPtr);
return result;
HTTP::Parser H;
H.url = "/CAB/keyfile?PROTECTION-TYPE=PLAYREADY&TYPE=DTV&POSITION=0&RESOURCE-ID=" + vmData.name;
H.SetHeader("Host", vmData.url.substr(hostPos));
H.SendRequest(veriConn);
H.Clean();
while (veriConn && (!veriConn.spool() || !H.Read(veriConn))){}
vmData.key = binKey(H.GetHeader("Key"));
vmData.keyid = H.GetHeader("KeyId");
vmData.laurl = H.GetHeader("LAURL");
vmData.lauurl = H.GetHeader("LAUURL");
} }
void verimatrixData::read(const char *shmPage){ bool AES::encryptBlockCTR(uint64_t ivec, const char *src, char *dest, size_t dataLen){
int offset = 0; size_t ncOff = 0;
url = std::string(shmPage + offset); unsigned char streamBlock[] ={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
offset += url.size() + 1; //+1 for the concluding 0-byte
name = std::string(shmPage + offset);
offset += name.size() + 1; //+1 for the concluding 0-byte
key = std::string(shmPage + offset);
offset += key.size() + 1; //+1 for the concluding 0-byte
keyid = std::string(shmPage + offset);
offset += keyid.size() + 1; //+1 for the concluding 0-byte
laurl = std::string(shmPage + offset);
offset += laurl.size() + 1; //+1 for the concluding 0-byte
lauurl = std::string(shmPage + offset);
key = binKey(key); unsigned char nonceCtr[] ={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
Bit::htobll((char *)nonceCtr, ivec);
return mbedtls_aes_crypt_ctr(&ctx, dataLen, &ncOff, nonceCtr, streamBlock,
(const unsigned char *)src, (unsigned char *)dest) == 0;
} }
void verimatrixData::write(char *shmPage){ DTSC::Packet AES::encryptPacketCBC(const DTSC::Meta &M, const DTSC::Packet &src, char *ivec, size_t newTrack){
int offset = 0; DTSC::Packet res;
memcpy(shmPage + offset, url.c_str(), url.size() + 1); if (newTrack == INVALID_TRACK_ID){
offset += url.size() + 1; //+1 for the concluding 0-byte FAIL_MSG("No target track given for track encryption!");
memcpy(shmPage + offset, name.c_str(), name.size() + 1); return res;
offset += name.size() + 1; //+1 for the concluding 0-byte }
std::string tmpKey = hex(key);
memcpy(shmPage + offset, tmpKey.c_str(), tmpKey.size() + 1); char *data;
offset += tmpKey.size() + 1; //+1 for the concluding 0-byte size_t dataLen;
memcpy(shmPage + offset, keyid.c_str(), keyid.size() + 1); src.getString("data", data, dataLen);
offset += keyid.size() + 1; //+1 for the concluding 0-byte
memcpy(shmPage + offset, laurl.c_str(), laurl.size() + 1); size_t trackIdx = M.getSourceTrack(newTrack);
offset += laurl.size() + 1; //+1 for the concluding 0-byte
memcpy(shmPage + offset, lauurl.c_str(), lauurl.size() + 1); bool encrypt = false;
if (M.getCodec(trackIdx) == "H264"){
std::deque<nalu::nalData> nalUnits = h264::analysePackets(data, dataLen);
for (std::deque<nalu::nalData>::iterator it = nalUnits.begin(); it != nalUnits.end(); it++){
if (it->nalType != 1 && it->nalType != 5){continue;}
if (it->nalSize <= 48){continue;}
encrypt = true;
break;
}
}
if (!encrypt){
res.genericFill(src.getTime(), src.getInt("offset"), newTrack, data, dataLen, 0, src.getFlag("keyframe"));
return res;
}
char *encData = (char *)malloc(dataLen);
if (M.getCodec(trackIdx) == "H264"){
if (!encryptH264BlockFairplay(ivec, data, encData, dataLen)){
ERROR_MSG("Failed to encrypt a block of 16 bytes!");
free(encData);
return res;
}
}else{
INFO_MSG("Going to fully CBC encrypt a %s packet of %zu bytes", M.getType(trackIdx).c_str(), dataLen);
if (!encryptBlockCBC(ivec, data, encData, dataLen)){
FAIL_MSG("Failed to encrypt packet");
free(encData);
return res;
}
}
res.genericFill(src.getTime(), src.getInt("offset"), newTrack, encData, dataLen, 0, src.getFlag("keyframe"));
free(encData);
return res;
}
bool AES::encryptH264BlockFairplay(char *ivec, const char *src, char *dest, size_t dataLen){
size_t offset = 0;
std::deque<nalu::nalData> nalUnits = h264::analysePackets(src, dataLen);
for (std::deque<nalu::nalData>::iterator it = nalUnits.begin(); it != nalUnits.end(); it++){
if ((it->nalType != 1 && it->nalType != 5) || it->nalSize <= 48){
memcpy(dest + offset, src + offset, it->nalSize + 4);
offset += it->nalSize + 4;
continue;
}
memcpy(dest + offset, src + offset, 36);
offset += 36;
size_t encryptedBlocks = 0;
size_t lenToGo = it->nalSize - 32;
while (lenToGo){
if (lenToGo > 16){
if (!encryptBlockCBC(ivec, src + offset, dest + offset, 16)){
ERROR_MSG("Failed to encrypt a block of 16 bytes!");
return false;
}
offset += 16;
lenToGo -= 16;
++encryptedBlocks;
}
memcpy(dest + offset, src + offset, std::min(lenToGo, (size_t)144));
offset += std::min(lenToGo, (size_t)144);
lenToGo -= std::min(lenToGo, (size_t)144);
}
}
return true;
}
std::string AES::encryptBlockCBC(char *ivec, const std::string &inp){
char *resPtr = (char *)malloc(inp.size());
if (!encryptBlockCBC(ivec, inp.c_str(), resPtr, inp.size())){
free(resPtr);
return "";
}
std::string result(resPtr, inp.size());
free(resPtr);
return result;
}
bool AES::encryptBlockCBC(char *ivec, const char *src, char *dest, size_t dataLen){
if (dataLen % 16){WARN_MSG("Encrypting a non-multiple of 16 bytes: %zu", dataLen);}
return mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, dataLen, (unsigned char *)ivec,
(const unsigned char *)src, (unsigned char *)dest) == 0;
} }
}// namespace Encryption }// namespace Encryption

View file

@ -1,35 +1,28 @@
#pragma once #pragma once
#include "dtsc.h" #include "dtsc.h"
#include <mbedtls/aes.h>
#include <string> #include <string>
namespace Encryption{ namespace Encryption{
class verimatrixData{ class AES{
public: public:
void read(const char *shmPage); AES();
void write(char *shmPage); ~AES();
std::string url;
std::string name; void setEncryptKey(const char *key);
std::string key; void setDecryptKey(const char *key);
std::string keyid;
std::string keyseed; DTSC::Packet encryptPacketCTR(const DTSC::Meta &M, const DTSC::Packet &src, uint64_t ivec, size_t newTrack);
std::string laurl; std::string encryptBlockCTR(uint64_t ivec, const std::string &inp);
std::string lauurl; bool encryptBlockCTR(uint64_t ivec, const char *src, char *dest, size_t dataLen);
bool encryptH264BlockFairplay(char *ivec, const char *src, char *dest, size_t dataLen);
DTSC::Packet encryptPacketCBC(const DTSC::Meta &M, const DTSC::Packet &src, char *ivec, size_t newTrack);
std::string encryptBlockCBC(char *ivec, const std::string &inp);
bool encryptBlockCBC(char *ivec, const char *src, char *dest, size_t dataLen);
protected:
mbedtls_aes_context ctx;
}; };
std::string hexString(const char *data, unsigned long dataLen);
std::string AES_Crypt(const std::string &data, const std::string &key, std::string &ivec);
std::string AES_Crypt(const char *data, int dataLen, const char *key, const char *ivec);
// These functions are dangerous for your data
void AESFullCrypt(char *data, int dataLen, const char *key, const char *ivec);
void AESPartialCrypt(char *data, int dataLen, char *expandedKey, char *eCount, char *iVec,
unsigned int &num, bool &initialize);
std::string PR_GenerateContentKey(std::string &keyseed, std::string &keyid);
std::string PR_GuidToByteArray(std::string &guid);
void encryptPlayReady(DTSC::Packet &pack, std::string &codec, const char *iVec, const char *key);
void fillVerimatrix(verimatrixData &vmData);
}// namespace Encryption }// namespace Encryption

View file

@ -4,6 +4,7 @@
#include "adts.h" #include "adts.h"
#include "defines.h" #include "defines.h"
#include "flv_tag.h" #include "flv_tag.h"
#include "mp4_generic.h"
#include "rtmpchunks.h" #include "rtmpchunks.h"
#include "timing.h" #include "timing.h"
#include "util.h" #include "util.h"
@ -323,17 +324,23 @@ FLV::Tag &FLV::Tag::operator=(const FLV::Tag &O){
return *this; return *this;
}// assignment operator }// assignment operator
bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, DTSC::Track &track){ bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, const DTSC::Meta &M, size_t idx){
std::string meta_str; std::string meta_str;
len = 0; len = 0;
if (track.type == "video"){ if (idx == INVALID_TRACK_ID){
WARN_MSG("packet with invalid track id found!");
return false;
}
std::string type = M.getType(idx);
std::string codec = M.getCodec(idx);
if (type == "video"){
char *tmpData = 0; char *tmpData = 0;
size_t tmpLen = 0; size_t tmpLen = 0;
packData.getString("data", tmpData, tmpLen); packData.getString("data", tmpData, tmpLen);
len = tmpLen + 16; len = tmpLen + 16;
if (track.codec == "H264"){len += 4;} if (codec == "H264"){len += 4;}
if (!checkBufferSize()){return false;} if (!checkBufferSize()){return false;}
if (track.codec == "H264"){ if (codec == "H264"){
memcpy(data + 16, tmpData, len - 20); memcpy(data + 16, tmpData, len - 20);
data[12] = 1; data[12] = 1;
offset(packData.getInt("offset")); offset(packData.getInt("offset"));
@ -341,13 +348,13 @@ bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, DTSC::Track &track){
memcpy(data + 12, tmpData, len - 16); memcpy(data + 12, tmpData, len - 16);
} }
data[11] = 0; data[11] = 0;
if (track.codec == "H264"){data[11] |= 7;} if (codec == "H264"){data[11] |= 7;}
if (track.codec == "ScreenVideo2"){data[11] |= 6;} if (codec == "ScreenVideo2"){data[11] |= 6;}
if (track.codec == "VP6Alpha"){data[11] |= 5;} if (codec == "VP6Alpha"){data[11] |= 5;}
if (track.codec == "VP6"){data[11] |= 4;} if (codec == "VP6"){data[11] |= 4;}
if (track.codec == "ScreenVideo1"){data[11] |= 3;} if (codec == "ScreenVideo1"){data[11] |= 3;}
if (track.codec == "H263"){data[11] |= 2;} if (codec == "H263"){data[11] |= 2;}
if (track.codec == "JPEG"){data[11] |= 1;} if (codec == "JPEG"){data[11] |= 1;}
if (packData.getFlag("keyframe")){ if (packData.getFlag("keyframe")){
data[11] |= 0x10; data[11] |= 0x10;
}else{ }else{
@ -355,32 +362,32 @@ bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, DTSC::Track &track){
} }
if (packData.getFlag("disposableframe")){data[11] |= 0x30;} if (packData.getFlag("disposableframe")){data[11] |= 0x30;}
} }
if (track.type == "audio"){ if (type == "audio"){
char *tmpData = 0; char *tmpData = 0;
size_t tmpLen = 0; size_t tmpLen = 0;
packData.getString("data", tmpData, tmpLen); packData.getString("data", tmpData, tmpLen);
len = tmpLen + 16; len = tmpLen + 16;
if (track.codec == "AAC"){len++;} if (codec == "AAC"){len++;}
if (!checkBufferSize()){return false;} if (!checkBufferSize()){return false;}
if (track.codec == "AAC"){ if (codec == "AAC"){
memcpy(data + 13, tmpData, len - 17); memcpy(data + 13, tmpData, len - 17);
data[12] = 1; // raw AAC data, not sequence header data[12] = 1; // raw AAC data, not sequence header
}else{ }else{
memcpy(data + 12, tmpData, len - 16); memcpy(data + 12, tmpData, len - 16);
} }
unsigned int datarate = track.rate; unsigned int datarate = M.getRate(idx);
data[11] = 0; data[11] = 0;
if (track.codec == "AAC"){data[11] |= 0xA0;} if (codec == "AAC"){data[11] |= 0xA0;}
if (track.codec == "MP3"){ if (codec == "MP3"){
if (datarate == 8000){ if (datarate == 8000){
data[11] |= 0xE0; data[11] |= 0xE0;
}else{ }else{
data[11] |= 0x20; data[11] |= 0x20;
} }
} }
if (track.codec == "ADPCM"){data[11] |= 0x10;} if (codec == "ADPCM"){data[11] |= 0x10;}
if (track.codec == "PCM"){data[11] |= 0x30;} if (codec == "PCM"){data[11] |= 0x30;}
if (track.codec == "Nellymoser"){ if (codec == "Nellymoser"){
if (datarate == 8000){ if (datarate == 8000){
data[11] |= 0x50; data[11] |= 0x50;
}else if (datarate == 16000){ }else if (datarate == 16000){
@ -389,9 +396,9 @@ bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, DTSC::Track &track){
data[11] |= 0x60; data[11] |= 0x60;
} }
} }
if (track.codec == "ALAW"){data[11] |= 0x70;} if (codec == "ALAW"){data[11] |= 0x70;}
if (track.codec == "ULAW"){data[11] |= 0x80;} if (codec == "ULAW"){data[11] |= 0x80;}
if (track.codec == "Speex"){data[11] |= 0xB0;} if (codec == "Speex"){data[11] |= 0xB0;}
if (datarate >= 44100){ if (datarate >= 44100){
data[11] |= 0x0C; data[11] |= 0x0C;
}else if (datarate >= 22050){ }else if (datarate >= 22050){
@ -399,14 +406,14 @@ bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, DTSC::Track &track){
}else if (datarate >= 11025){ }else if (datarate >= 11025){
data[11] |= 0x04; data[11] |= 0x04;
} }
if (track.size != 8){data[11] |= 0x02;} if (M.getSize(idx) != 8){data[11] |= 0x02;}
if (track.channels > 1){data[11] |= 0x01;} if (M.getChannels(idx) > 1){data[11] |= 0x01;}
} }
if (!len){return false;} if (!len){return false;}
setLen(); setLen();
if (track.type == "video"){data[0] = 0x09;} if (type == "video"){data[0] = 0x09;}
if (track.type == "audio"){data[0] = 0x08;} if (type == "audio"){data[0] = 0x08;}
if (track.type == "meta"){data[0] = 0x12;} if (type == "meta"){data[0] = 0x12;}
data[1] = ((len - 15) >> 16) & 0xFF; data[1] = ((len - 15) >> 16) & 0xFF;
data[2] = ((len - 15) >> 8) & 0xFF; data[2] = ((len - 15) >> 8) & 0xFF;
data[3] = (len - 15) & 0xFF; data[3] = (len - 15) & 0xFF;
@ -431,13 +438,14 @@ void FLV::Tag::setLen(){
} }
/// FLV Video init data loader function from metadata. /// FLV Video init data loader function from metadata.
bool FLV::Tag::DTSCVideoInit(DTSC::Track &video){ bool FLV::Tag::DTSCVideoInit(DTSC::Meta &meta, uint32_t vTrack){
// Unknown? Assume H264. // Unknown? Assume H264.
len = 0; len = 0;
if (video.codec == "?"){video.codec = "H264";} if (meta.getCodec(vTrack) == "?"){meta.setCodec(vTrack, "H264");}
if (video.codec == "H264"){len = video.init.size() + 20;} std::string initData = meta.getInit(vTrack);
if (meta.getCodec(vTrack) == "H264"){len = initData.size() + 20;}
if (len <= 0 || !checkBufferSize()){return false;} if (len <= 0 || !checkBufferSize()){return false;}
memcpy(data + 16, video.init.c_str(), len - 20); memcpy(data + 16, initData.c_str(), len - 20);
data[12] = 0; // H264 sequence header data[12] = 0; // H264 sequence header
data[13] = 0; data[13] = 0;
data[14] = 0; data[14] = 0;
@ -456,18 +464,19 @@ bool FLV::Tag::DTSCVideoInit(DTSC::Track &video){
} }
/// FLV Audio init data loader function from metadata. /// FLV Audio init data loader function from metadata.
bool FLV::Tag::DTSCAudioInit(DTSC::Track &audio){ bool FLV::Tag::DTSCAudioInit(DTSC::Meta &meta, uint32_t aTrack){
len = 0; len = 0;
// Unknown? Assume AAC. // Unknown? Assume AAC.
if (audio.codec == "?"){audio.codec = "AAC";} if (meta.getCodec(aTrack) == "?"){meta.setCodec(aTrack, "AAC");}
if (audio.codec == "AAC"){len = audio.init.size() + 17;} std::string initData = meta.getInit(aTrack);
if (meta.getCodec(aTrack) == "AAC"){len = initData.size() + 17;}
if (len <= 0 || !checkBufferSize()){return false;} if (len <= 0 || !checkBufferSize()){return false;}
memcpy(data + 13, audio.init.c_str(), len - 17); memcpy(data + 13, initData.c_str(), len - 17);
data[12] = 0; // AAC sequence header data[12] = 0; // AAC sequence header
data[11] = 0; data[11] = 0;
if (audio.codec == "AAC"){data[11] += 0xA0;} if (meta.getCodec(aTrack) == "AAC"){data[11] += 0xA0;}
if (audio.codec == "MP3"){data[11] += 0x20;} if (meta.getCodec(aTrack) == "MP3"){data[11] += 0x20;}
unsigned int datarate = audio.rate; unsigned int datarate = meta.getRate(aTrack);
if (datarate >= 44100){ if (datarate >= 44100){
data[11] += 0x0C; data[11] += 0x0C;
}else if (datarate >= 22050){ }else if (datarate >= 22050){
@ -475,8 +484,8 @@ bool FLV::Tag::DTSCAudioInit(DTSC::Track &audio){
}else if (datarate >= 11025){ }else if (datarate >= 11025){
data[11] += 0x04; data[11] += 0x04;
} }
if (audio.size != 8){data[11] += 0x02;} if (meta.getSize(aTrack) != 8){data[11] += 0x02;}
if (audio.channels > 1){data[11] += 0x01;} if (meta.getChannels(aTrack) > 1){data[11] += 0x01;}
setLen(); setLen();
data[0] = 0x08; data[0] = 0x08;
data[1] = ((len - 15) >> 16) & 0xFF; data[1] = ((len - 15) >> 16) & 0xFF;
@ -489,86 +498,87 @@ bool FLV::Tag::DTSCAudioInit(DTSC::Track &audio){
return true; return true;
} }
bool FLV::Tag::DTSCMetaInit(DTSC::Meta &M, std::set<long unsigned int> &selTracks){ bool FLV::Tag::DTSCMetaInit(const DTSC::Meta &M, std::set<long unsigned int> &selTracks){
AMF::Object amfdata("root", AMF::AMF0_DDV_CONTAINER); AMF::Object amfdata("root", AMF::AMF0_DDV_CONTAINER);
amfdata.addContent(AMF::Object("", "onMetaData")); amfdata.addContent(AMF::Object("", "onMetaData"));
amfdata.addContent(AMF::Object("", AMF::AMF0_ECMA_ARRAY)); amfdata.addContent(AMF::Object("", AMF::AMF0_ECMA_ARRAY));
AMF::Object trinfo = AMF::Object("trackinfo", AMF::AMF0_STRICT_ARRAY); AMF::Object trinfo = AMF::Object("trackinfo", AMF::AMF0_STRICT_ARRAY);
int i = 0; int i = 0;
unsigned long long mediaLen = 0; uint64_t mediaLen = 0;
for (std::set<long unsigned int>::iterator it = selTracks.begin(); it != selTracks.end(); it++){ for (std::set<long unsigned int>::iterator it = selTracks.begin(); it != selTracks.end(); it++){
if (M.tracks[*it].lastms - M.tracks[*it].firstms > mediaLen){ if (M.getLastms(*it) - M.getFirstms(*it) > mediaLen){
mediaLen = M.tracks[*it].lastms - M.tracks[*it].firstms; mediaLen = M.getLastms(*it) - M.getFirstms(*it);
} }
if (M.tracks[*it].type == "video"){ if (M.getType(*it) == "video"){
trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT)); trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT));
trinfo.getContentP(i)->addContent(AMF::Object( trinfo.getContentP(i)->addContent(AMF::Object(
"length", ((double)M.tracks[*it].lastms / 1000) * ((double)M.tracks[*it].fpks / 1000.0), AMF::AMF0_NUMBER)); "length", ((double)M.getLastms(*it) / 1000) * ((double)M.getFpks(*it) / 1000.0), AMF::AMF0_NUMBER));
trinfo.getContentP(i)->addContent( trinfo.getContentP(i)->addContent(AMF::Object("timescale", ((double)M.getFpks(*it) / 1000), AMF::AMF0_NUMBER));
AMF::Object("timescale", ((double)M.tracks[*it].fpks / 1000.0), AMF::AMF0_NUMBER));
trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY));
amfdata.getContentP(1)->addContent(AMF::Object("hasVideo", 1, AMF::AMF0_BOOL)); amfdata.getContentP(1)->addContent(AMF::Object("hasVideo", 1, AMF::AMF0_BOOL));
if (M.tracks[*it].codec == "H264"){ std::string codec = M.getCodec(*it);
if (codec == "H264"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 7, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 7, AMF::AMF0_NUMBER));
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "avc1")); trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "avc1"));
} }
if (M.tracks[*it].codec == "ScreenVideo2"){ if (codec == "ScreenVideo2"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 6, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 6, AMF::AMF0_NUMBER));
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "sv2")); trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "sv2"));
} }
if (M.tracks[*it].codec == "VP6Alpha"){ if (codec == "VP6Alpha"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 5, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 5, AMF::AMF0_NUMBER));
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "vp6a")); trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "vp6a"));
} }
if (M.tracks[*it].codec == "VP6"){ if (codec == "VP6"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 4, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 4, AMF::AMF0_NUMBER));
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "vp6")); trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "vp6"));
} }
if (M.tracks[*it].codec == "ScreenVideo1"){ if (codec == "ScreenVideo1"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 3, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 3, AMF::AMF0_NUMBER));
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "sv1")); trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "sv1"));
} }
if (M.tracks[*it].codec == "H263"){ if (codec == "H263"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 2, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 2, AMF::AMF0_NUMBER));
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "h263")); trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "h263"));
} }
if (M.tracks[*it].codec == "JPEG"){ if (codec == "JPEG"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 1, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 1, AMF::AMF0_NUMBER));
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "jpeg")); trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "jpeg"));
} }
amfdata.getContentP(1)->addContent(AMF::Object("width", M.tracks[*it].width, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("width", M.getWidth(*it), AMF::AMF0_NUMBER));
amfdata.getContentP(1)->addContent(AMF::Object("height", M.tracks[*it].height, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("height", M.getHeight(*it), AMF::AMF0_NUMBER));
amfdata.getContentP(1)->addContent( amfdata.getContentP(1)->addContent(
AMF::Object("videoframerate", (double)M.tracks[*it].fpks / 1000.0, AMF::AMF0_NUMBER)); AMF::Object("videoframerate", (double)M.getFpks(*it) / 1000.0, AMF::AMF0_NUMBER));
amfdata.getContentP(1)->addContent( amfdata.getContentP(1)->addContent(
AMF::Object("videodatarate", (double)M.tracks[*it].bps / 128.0, AMF::AMF0_NUMBER)); AMF::Object("videodatarate", (double)M.getBps(*it) / 128.0, AMF::AMF0_NUMBER));
++i; ++i;
} }
if (M.tracks[*it].type == "audio"){ if (M.getType(*it) == "audio"){
trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT)); trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT));
trinfo.getContentP(i)->addContent(AMF::Object( trinfo.getContentP(i)->addContent(
"length", ((double)M.tracks[*it].lastms) * ((double)M.tracks[*it].rate), AMF::AMF0_NUMBER)); AMF::Object("length", (double)(M.getLastms(*it) * M.getRate(*it)), AMF::AMF0_NUMBER));
trinfo.getContentP(i)->addContent(AMF::Object("timescale", M.tracks[*it].rate, AMF::AMF0_NUMBER)); trinfo.getContentP(i)->addContent(AMF::Object("timescale", M.getRate(*it), AMF::AMF0_NUMBER));
trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY));
amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL)); amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL));
amfdata.getContentP(1)->addContent(AMF::Object("audiodelay", 0.0, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("audiodelay", 0.0, AMF::AMF0_NUMBER));
if (M.tracks[*it].codec == "AAC"){ std::string codec = M.getCodec(*it);
amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", (std::string) "mp4a")); if (codec == "AAC"){
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "mp4a")); amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", "mp4a"));
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "mp4a"));
} }
if (M.tracks[*it].codec == "MP3"){ if (codec == "MP3"){
amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", (std::string) "mp3")); amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", "mp3"));
trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "mp3")); trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "mp3"));
} }
amfdata.getContentP(1)->addContent(AMF::Object("audiochannels", M.tracks[*it].channels, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("audiochannels", M.getChannels(*it), AMF::AMF0_NUMBER));
amfdata.getContentP(1)->addContent(AMF::Object("audiosamplerate", M.tracks[*it].rate, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("audiosamplerate", M.getRate(*it), AMF::AMF0_NUMBER));
amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", M.tracks[*it].size, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", M.getSize(*it), AMF::AMF0_NUMBER));
amfdata.getContentP(1)->addContent( amfdata.getContentP(1)->addContent(
AMF::Object("audiodatarate", (double)M.tracks[*it].bps / 128.0, AMF::AMF0_NUMBER)); AMF::Object("audiodatarate", (double)M.getBps(*it) / 128.0, AMF::AMF0_NUMBER));
++i; ++i;
} }
} }
if (M.vod){ if (M.getVod()){
amfdata.getContentP(1)->addContent(AMF::Object("duration", mediaLen / 1000, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("duration", mediaLen / 1000, AMF::AMF0_NUMBER));
} }
amfdata.getContentP(1)->addContent(trinfo); amfdata.getContentP(1)->addContent(trinfo);
@ -742,8 +752,8 @@ bool FLV::Tag::FileLoader(FILE *f){
}else{ }else{
// if a tag header, calculate length and read tag body // if a tag header, calculate length and read tag body
len = data[3] + 15; len = data[3] + 15;
len += (data[2] << 8); len += (((unsigned int)data[2]) << 8);
len += (data[1] << 16); len += (((unsigned int)data[1]) << 16);
if (!checkBufferSize()){return false;} if (!checkBufferSize()){return false;}
if (data[0] > 0x12){ if (data[0] > 0x12){
data[0] += 32; data[0] += 32;
@ -781,7 +791,7 @@ unsigned int FLV::Tag::getTrackID(){
case 0x08: return 2; // audio track case 0x08: return 2; // audio track
case 0x09: return 1; // video track case 0x09: return 1; // video track
case 0x12: return 3; // meta track case 0x12: return 3; // meta track
default: return 0; default: return INVALID_TRACK_ID;
} }
} }
@ -806,13 +816,16 @@ unsigned int FLV::Tag::getDataLen(){
return len - 16; return len - 16;
} }
void FLV::Tag::toMeta(DTSC::Meta &metadata, AMF::Object &amf_storage, unsigned int reTrack){ void FLV::Tag::toMeta(DTSC::Meta &meta, AMF::Object &amf_storage){
if (!reTrack){ size_t reTrack = INVALID_TRACK_ID;
switch (data[0]){ toMeta(meta, amf_storage, reTrack);
case 0x09: reTrack = 1; break; // video }
case 0x08: reTrack = 2; break; // audio void FLV::Tag::toMeta(DTSC::Meta &meta, AMF::Object &amf_storage, size_t &reTrack){
case 0x12: reTrack = 3; break; // meta std::string trackType;
} switch (data[0]){
case 0x09: trackType = "video"; break; // video
case 0x08: trackType = "audio"; break; // audio
case 0x12: trackType = "meta"; break; // meta
} }
if (data[0] == 0x12){ if (data[0] == 0x12){
@ -828,45 +841,44 @@ void FLV::Tag::toMeta(DTSC::Meta &metadata, AMF::Object &amf_storage, unsigned i
if (tmp){amf_storage = *tmp;} if (tmp){amf_storage = *tmp;}
return; return;
} }
if (data[0] == 0x08 && (metadata.tracks[reTrack].codec == "" || metadata.tracks[reTrack].codec != getAudioCodec() ||
(needsInitData() && isInitData()))){ if (meta.getVod() && reTrack == INVALID_TRACK_ID){
reTrack = meta.trackIDToIndex(getTrackID(), getpid());
}
if (reTrack == INVALID_TRACK_ID){
reTrack = meta.addTrack();
meta.setID(reTrack, getTrackID());
}
std::string codec = meta.getCodec(reTrack);
if (data[0] == 0x08 && (codec == "" || codec != getAudioCodec() || (needsInitData() && isInitData()))){
char audiodata = data[11]; char audiodata = data[11];
metadata.tracks[reTrack].trackID = reTrack; meta.setType(reTrack, "audio");
metadata.tracks[reTrack].type = "audio"; meta.setCodec(reTrack, getAudioCodec());
metadata.tracks[reTrack].codec = getAudioCodec();
switch (audiodata & 0x0C){ switch (audiodata & 0x0C){
case 0x0: metadata.tracks[reTrack].rate = 5512; break; case 0x0: meta.setRate(reTrack, 5512); break;
case 0x4: metadata.tracks[reTrack].rate = 11025; break; case 0x4: meta.setRate(reTrack, 11025); break;
case 0x8: metadata.tracks[reTrack].rate = 22050; break; case 0x8: meta.setRate(reTrack, 22050); break;
case 0xC: metadata.tracks[reTrack].rate = 44100; break; case 0xC: meta.setRate(reTrack, 44100); break;
} }
if (amf_storage.getContentP("audiosamplerate")){ if (amf_storage.getContentP("audiosamplerate")){
metadata.tracks[reTrack].rate = (long long int)amf_storage.getContentP("audiosamplerate")->NumValue(); meta.setRate(reTrack, amf_storage.getContentP("audiosamplerate")->NumValue());
}
switch (audiodata & 0x02){
case 0x0: metadata.tracks[reTrack].size = 8; break;
case 0x2: metadata.tracks[reTrack].size = 16; break;
} }
meta.setSize(reTrack, audiodata & 0x02 ? 16 : 8);
if (amf_storage.getContentP("audiosamplesize")){ if (amf_storage.getContentP("audiosamplesize")){
metadata.tracks[reTrack].size = (long long int)amf_storage.getContentP("audiosamplesize")->NumValue(); meta.setSize(reTrack, amf_storage.getContentP("audiosamplesize")->NumValue());
}
switch (audiodata & 0x01){
case 0x0: metadata.tracks[reTrack].channels = 1; break;
case 0x1: metadata.tracks[reTrack].channels = 2; break;
} }
meta.setChannels(reTrack, audiodata & 0x01 ? 2 : 1);
if (amf_storage.getContentP("stereo")){ if (amf_storage.getContentP("stereo")){
if (amf_storage.getContentP("stereo")->NumValue() == 1){ meta.setChannels(reTrack, amf_storage.getContentP("stereo")->NumValue() == 1 ? 2 : 1);
metadata.tracks[reTrack].channels = 2;
}else{
metadata.tracks[reTrack].channels = 1;
}
} }
if (needsInitData() && isInitData()){ if (needsInitData() && isInitData()){
if ((audiodata & 0xF0) == 0xA0){ if ((audiodata & 0xF0) == 0xA0){
metadata.tracks[reTrack].init = std::string((char *)data + 13, (size_t)len - 17); meta.setInit(reTrack, data + 13, len - 17);
}else{ }else{
metadata.tracks[reTrack].init = std::string((char *)data + 12, (size_t)len - 16); meta.setInit(reTrack, data + 12, len - 16);
} }
if (metadata.tracks[reTrack].codec == "AAC"){ if (metadata.tracks[reTrack].codec == "AAC"){
metadata.tracks[reTrack].rate = aac::AudSpecConf::rate(metadata.tracks[reTrack].init); metadata.tracks[reTrack].rate = aac::AudSpecConf::rate(metadata.tracks[reTrack].init);
@ -875,44 +887,47 @@ void FLV::Tag::toMeta(DTSC::Meta &metadata, AMF::Object &amf_storage, unsigned i
} }
} }
if (data[0] == 0x09 && ((needsInitData() && isInitData()) || !metadata.tracks[reTrack].codec.size())){ if (data[0] == 0x09 && ((needsInitData() && isInitData()) || !codec.size())){
char videodata = data[11]; char videodata = data[11];
metadata.tracks[reTrack].codec = getVideoCodec(); meta.setCodec(reTrack, getVideoCodec());
metadata.tracks[reTrack].type = "video"; meta.setType(reTrack, "video");
metadata.tracks[reTrack].trackID = reTrack;
if (amf_storage.getContentP("width")){ if (amf_storage.getContentP("width")){
metadata.tracks[reTrack].width = (long long int)amf_storage.getContentP("width")->NumValue(); meta.setWidth(reTrack, amf_storage.getContentP("width")->NumValue());
} }
if (amf_storage.getContentP("height")){ if (amf_storage.getContentP("height")){
metadata.tracks[reTrack].height = (long long int)amf_storage.getContentP("height")->NumValue(); meta.setHeight(reTrack, amf_storage.getContentP("height")->NumValue());
} }
if (!metadata.tracks[reTrack].fpks && amf_storage.getContentP("videoframerate")){ if (!meta.getFpks(reTrack) && amf_storage.getContentP("videoframerate")){
if (amf_storage.getContentP("videoframerate")->NumValue()){ if (amf_storage.getContentP("videoframerate")->NumValue()){
metadata.tracks[reTrack].fpks = meta.setFpks(reTrack, amf_storage.getContentP("videoframerate")->NumValue() * 1000.0);
(long long int)(amf_storage.getContentP("videoframerate")->NumValue() * 1000.0);
}else{ }else{
metadata.tracks[reTrack].fpks = meta.setFpks(reTrack, atoi(amf_storage.getContentP("videoframerate")->StrValue().c_str()) * 1000.0);
atoi(amf_storage.getContentP("videoframerate")->StrValue().c_str()) * 1000.0;
} }
} }
if (needsInitData() && isInitData()){ if (needsInitData() && isInitData()){
if ((videodata & 0x0F) == 7){ if ((videodata & 0x0F) == 7){
if (len < 21){return;} if (len < 21){return;}
metadata.tracks[reTrack].init = std::string((char *)data + 16, (size_t)len - 20); MP4::AVCC avccBox;
avccBox.setPayload(data + 16, len - 20);
avccBox.sanitize();
meta.setInit(reTrack, avccBox.payload(), avccBox.payloadSize());
}else{ }else{
if (len < 17){return;} if (len < 17){return;}
metadata.tracks[reTrack].init = std::string((char *)data + 12, (size_t)len - 16); MP4::AVCC avccBox;
avccBox.setPayload(data + 12, len - 16);
avccBox.sanitize();
meta.setInit(reTrack, avccBox.payload(), avccBox.payloadSize());
} }
/// this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but /// this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but
/// we do need correct data... /// we do need correct data...
if (!metadata.tracks[reTrack].width || !metadata.tracks[reTrack].height || if (!meta.getWidth(reTrack) || !meta.getHeight(reTrack) || !meta.getFpks(reTrack)){
!metadata.tracks[reTrack].fpks){ std::string init = meta.getInit(reTrack);
h264::sequenceParameterSet sps; h264::sequenceParameterSet sps;
sps.fromDTSCInit(metadata.tracks[reTrack].init); sps.fromDTSCInit(init);
h264::SPSMeta spsChar = sps.getCharacteristics(); h264::SPSMeta spsChar = sps.getCharacteristics();
metadata.tracks[reTrack].width = spsChar.width; meta.setWidth(reTrack, spsChar.width);
metadata.tracks[reTrack].height = spsChar.height; meta.setHeight(reTrack, spsChar.height);
metadata.tracks[reTrack].fpks = spsChar.fps * 1000; meta.setFpks(reTrack, spsChar.fps * 1000);
} }
} }
} }

View file

@ -49,11 +49,12 @@ namespace FLV{
~Tag(); ///< Generic destructor. ~Tag(); ///< Generic destructor.
// loader functions // loader functions
bool ChunkLoader(const RTMPStream::Chunk &O); bool ChunkLoader(const RTMPStream::Chunk &O);
bool DTSCLoader(DTSC::Packet &packData, DTSC::Track &track); bool DTSCLoader(DTSC::Packet &packData, const DTSC::Meta &M, size_t idx);
bool DTSCVideoInit(DTSC::Track &video); bool DTSCVideoInit(DTSC::Meta &meta, uint32_t vTrack);
bool DTSCAudioInit(DTSC::Track &audio); bool DTSCAudioInit(DTSC::Meta &meta, uint32_t aTrack);
bool DTSCMetaInit(DTSC::Meta &M, std::set<long unsigned int> &selTracks); bool DTSCMetaInit(const DTSC::Meta &M, std::set<long unsigned int> &selTracks);
void toMeta(DTSC::Meta &metadata, AMF::Object &amf_storage, unsigned int reTrack = 0); void toMeta(DTSC::Meta &meta, AMF::Object &amf_storage);
void toMeta(DTSC::Meta &meta, AMF::Object &amf_storage, size_t &reTrack);
bool MemLoader(char *D, unsigned int S, unsigned int &P); bool MemLoader(char *D, unsigned int S, unsigned int &P);
bool FileLoader(FILE *f); bool FileLoader(FILE *f);
unsigned int getTrackID(); unsigned int getTrackID();

View file

@ -1039,7 +1039,6 @@ namespace h264{
if (videoSignalTypePresentFlag){ if (videoSignalTypePresentFlag){
videoFormat = bs.get(3); videoFormat = bs.get(3);
videoFullRangeFlag = bs.get(1); videoFullRangeFlag = bs.get(1);
;
colourDescriptionPresentFlag = bs.get(1); colourDescriptionPresentFlag = bs.get(1);
if (colourDescriptionPresentFlag){ if (colourDescriptionPresentFlag){
colourPrimaries = bs.get(8); colourPrimaries = bs.get(8);

View file

@ -44,7 +44,6 @@ namespace h264{
uint32_t chroma_format_idc; ///< the value of chroma_format_idc uint32_t chroma_format_idc; ///< the value of chroma_format_idc
std::string MyData; ///< The h264 nal unit data std::string MyData; ///< The h264 nal unit data
}; };
// NAL class
/// Special instance of NAL class for analyzing SPS nal units /// Special instance of NAL class for analyzing SPS nal units
class SPS : public NAL{ class SPS : public NAL{
@ -96,7 +95,6 @@ namespace h264{
virtual void setSPSNumber(size_t newNumber){} virtual void setSPSNumber(size_t newNumber){}
virtual void setPPSNumber(size_t newNumber){} virtual void setPPSNumber(size_t newNumber){}
protected:
std::string payload; std::string payload;
}; };

View file

@ -509,8 +509,7 @@ namespace h265{
profileTierLevel(bs, maxSubLayersMinus1, res); profileTierLevel(bs, maxSubLayersMinus1, res);
bs.getUExpGolomb(); bs.getUExpGolomb();
uint64_t chromaFormatIdc = bs.getUExpGolomb(); uint64_t chromaFormatIdc = bs.getUExpGolomb();
bool separateColorPlane = false; if (chromaFormatIdc == 3){bs.skip(1);}
if (chromaFormatIdc == 3){separateColorPlane = bs.get(1);}
res.width = bs.getUExpGolomb(); res.width = bs.getUExpGolomb();
res.height = bs.getUExpGolomb(); res.height = bs.getUExpGolomb();
bool conformanceWindow = bs.get(1); bool conformanceWindow = bs.get(1);

View file

@ -275,7 +275,8 @@ void HTTP::Parser::SendResponse(std::string code, std::string message, Socket::C
void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Parser &request, void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Parser &request,
Socket::Connection &conn, bool bufferAllChunks){ Socket::Connection &conn, bool bufferAllChunks){
std::string prot = request.protocol; std::string prot = request.protocol;
sendingChunks = (!bufferAllChunks && protocol == "HTTP/1.1" && request.GetHeader("Connection") != "close"); sendingChunks =
(!bufferAllChunks && request.protocol == "HTTP/1.1" && request.GetHeader("Connection") != "close");
CleanPreserveHeaders(); CleanPreserveHeaders();
protocol = prot; protocol = prot;
if (sendingChunks){ if (sendingChunks){

View file

@ -1,5 +1,4 @@
/// \file json.h Holds all JSON-related headers. /// \file json.h Holds all JSON-related headers.
#pragma once #pragma once
#include "socket.h" #include "socket.h"
#include <deque> #include <deque>

View file

@ -80,6 +80,7 @@ namespace MP4{
class fullBox : public Box{ class fullBox : public Box{
public: public:
fullBox(); fullBox();
fullBox(const Box &rs) : Box(rs){}
void setVersion(char newVersion); void setVersion(char newVersion);
char getVersion() const; char getVersion() const;
void setFlags(uint32_t newFlags); void setFlags(uint32_t newFlags);

View file

@ -4,9 +4,10 @@
#include "mp4_generic.h" #include "mp4_generic.h"
namespace MP4{ namespace MP4{
MFHD::MFHD(){ MFHD::MFHD(uint32_t sequenceNumber){
memcpy(data + 4, "mfhd", 4); memcpy(data + 4, "mfhd", 4);
setInt32(0, 0); setInt32(0, 0);
setSequenceNumber(sequenceNumber);
} }
void MFHD::setSequenceNumber(uint32_t newSequenceNumber){setInt32(newSequenceNumber, 4);} void MFHD::setSequenceNumber(uint32_t newSequenceNumber){setInt32(newSequenceNumber, 4);}
@ -1381,7 +1382,9 @@ namespace MP4{
} }
// Note: next 4 headers inherit from fullBox, start at byte 4. // Note: next 4 headers inherit from fullBox, start at byte 4.
VMHD::VMHD(){ VMHD::VMHD(uint32_t version, uint32_t flags){
setVersion(version);
setFlags(flags);
memcpy(data + 4, "vmhd", 4); memcpy(data + 4, "vmhd", 4);
setGraphicsMode(0); setGraphicsMode(0);
setOpColor(0, 0); setOpColor(0, 0);
@ -1563,7 +1566,7 @@ namespace MP4{
uint32_t offset = 8; // start of boxes uint32_t offset = 8; // start of boxes
for (i = 0; i < getEntryCount() && i < index; i++){offset += getBoxLen(offset);} for (i = 0; i < getEntryCount() && i < index; i++){offset += getBoxLen(offset);}
if (index + 1 > getEntryCount()){ if (index + 1 > getEntryCount()){
int amount = index + 1 - getEntryCount(); int amount = index - getEntryCount();
if (!reserve(payloadOffset + offset, 0, amount * 8)){return;} if (!reserve(payloadOffset + offset, 0, amount * 8)){return;}
for (int j = 0; j < amount; ++j){ for (int j = 0; j < amount; ++j){
memcpy(data + payloadOffset + offset + j * 8, "\000\000\000\010erro", 8); memcpy(data + payloadOffset + offset + j * 8, "\000\000\000\010erro", 8);
@ -1922,14 +1925,14 @@ namespace MP4{
setHeight(height); setHeight(height);
} }
TKHD::TKHD(DTSC::Track &track, bool fragmented){ TKHD::TKHD(const DTSC::Meta &M, size_t idx){
initialize(); initialize();
setTrackID(track.trackID); setTrackID(idx + 1);
setDuration(-1); setDuration(-1);
if (!fragmented){setDuration(track.lastms - track.firstms);} if (M.getVod()){setDuration(M.getLastms(idx) - M.getFirstms(idx));}
if (track.type == "video"){ if (M.getType(idx) == "video"){
setWidth(track.width); setWidth(M.getWidth(idx));
setHeight(track.height); setHeight(M.getHeight(idx));
} }
} }
@ -2136,7 +2139,7 @@ namespace MP4{
return r.str(); return r.str();
} }
MDHD::MDHD(uint64_t duration){ MDHD::MDHD(uint64_t duration, const std::string &language){
memcpy(data + 4, "mdhd", 4); memcpy(data + 4, "mdhd", 4);
// reserve an entire version 0 box // reserve an entire version 0 box
if (!reserve(0, 9, 32)){ if (!reserve(0, 9, 32)){
@ -2146,6 +2149,7 @@ namespace MP4{
setTimeScale(1000); setTimeScale(1000);
setDuration(duration); setDuration(duration);
setLanguage(language);
} }
void MDHD::setCreationTime(uint64_t newCreationTime){ void MDHD::setCreationTime(uint64_t newCreationTime){
@ -2643,22 +2647,23 @@ namespace MP4{
VisualSampleEntry::VisualSampleEntry(){initialize();} VisualSampleEntry::VisualSampleEntry(){initialize();}
VisualSampleEntry::VisualSampleEntry(DTSC::Track &track){ VisualSampleEntry::VisualSampleEntry(const DTSC::Meta &M, size_t idx){
std::string tCodec = M.getCodec(idx);
initialize(); initialize();
setDataReferenceIndex(1); setDataReferenceIndex(1);
setWidth(track.width); setWidth(M.getWidth(idx));
setHeight(track.height); setHeight(M.getHeight(idx));
if (track.codec == "H264"){ if (tCodec == "H264"){
setCodec("avc1"); setCodec("avc1");
MP4::AVCC avccBox; MP4::AVCC avccBox;
avccBox.setPayload(track.init); avccBox.setPayload(M.getInit(idx));
setCLAP(avccBox); setCLAP(avccBox);
} }
/*LTS-START*/ /*LTS-START*/
if (track.codec == "HEVC"){ if (tCodec == "HEVC"){
setCodec("hev1"); setCodec("hev1");
MP4::HVCC hvccBox; MP4::HVCC hvccBox;
hvccBox.setPayload(track.init); hvccBox.setPayload(M.getInit(idx));
setCLAP(hvccBox); setCLAP(hvccBox);
} }
/*LTS-END*/ /*LTS-END*/
@ -2678,6 +2683,8 @@ namespace MP4{
void VisualSampleEntry::setCodec(const char *newCodec){memcpy(data + 4, newCodec, 4);} void VisualSampleEntry::setCodec(const char *newCodec){memcpy(data + 4, newCodec, 4);}
std::string VisualSampleEntry::getCodec(){return std::string(data + 4, 4);}
void VisualSampleEntry::setWidth(uint16_t newWidth){setInt16(newWidth, 24);} void VisualSampleEntry::setWidth(uint16_t newWidth){setInt16(newWidth, 24);}
uint16_t VisualSampleEntry::getWidth(){return getInt16(24);} uint16_t VisualSampleEntry::getWidth(){return getInt16(24);}
@ -2815,19 +2822,20 @@ namespace MP4{
AudioSampleEntry::AudioSampleEntry(){initialize();} AudioSampleEntry::AudioSampleEntry(){initialize();}
AudioSampleEntry::AudioSampleEntry(DTSC::Track &track){ AudioSampleEntry::AudioSampleEntry(const DTSC::Meta &M, size_t idx){
std::string tCodec = M.getCodec(idx);
initialize(); initialize();
if (track.codec == "AAC" || track.codec == "MP3"){setCodec("mp4a");} if (tCodec == "AAC" || tCodec == "MP3"){setCodec("mp4a");}
if (track.codec == "AC3"){setCodec("ac-3");} if (tCodec == "AC3"){setCodec("ac-3");}
setDataReferenceIndex(1); setDataReferenceIndex(1);
setSampleRate(track.rate); setSampleRate(M.getRate(idx));
setChannelCount(track.channels); setChannelCount(M.getChannels(idx));
setSampleSize(track.size); setSampleSize(M.getSize(idx));
if (track.codec == "AC3"){ if (tCodec == "AC3"){
MP4::DAC3 dac3Box(track.rate, track.channels); MP4::DAC3 dac3Box(M.getRate(idx), M.getChannels(idx));
setCodecBox(dac3Box); setCodecBox(dac3Box);
}else{// other codecs use the ESDS box }else{// other codecs use the ESDS box
MP4::ESDS esdsBox(track.init); MP4::ESDS esdsBox(M.getInit(idx));
setCodecBox(esdsBox); setCodecBox(esdsBox);
} }
} }
@ -2938,14 +2946,14 @@ namespace MP4{
return r.str(); return r.str();
} }
TextSampleEntry::TextSampleEntry(DTSC::Track &track){ TextSampleEntry::TextSampleEntry(const DTSC::Meta &M, size_t idx){
initialize(); initialize();
if (track.codec == "subtitle"){ if (M.getCodec(idx) == "subtitle"){
setCodec("tx3g"); setCodec("tx3g");
}else{ }else{
// not supported codec // not supported codec
INFO_MSG("not supported codec: %s", track.codec.c_str()); INFO_MSG("not supported codec: %s", M.getCodec(idx).c_str());
} }
} }
@ -3258,7 +3266,11 @@ namespace MP4{
return toPrettyCFBString(indent, "[meta] Meta Box"); return toPrettyCFBString(indent, "[meta] Meta Box");
} }
ELST::ELST(){memcpy(data + 4, "elst", 4);} ELST::ELST(){
memcpy(data + 4, "elst", 4);
setVersion(0);
setFlags(0);
}
void ELST::setCount(uint32_t newVal){setInt32(newVal, 4);} void ELST::setCount(uint32_t newVal){setInt32(newVal, 4);}

View file

@ -8,7 +8,7 @@ namespace h265{
namespace MP4{ namespace MP4{
class MFHD : public Box{ class MFHD : public Box{
public: public:
MFHD(); MFHD(uint32_t sequenceNumber = 0);
void setSequenceNumber(uint32_t newSequenceNumber); void setSequenceNumber(uint32_t newSequenceNumber);
uint32_t getSequenceNumber(); uint32_t getSequenceNumber();
std::string toPrettyString(uint32_t indent = 0); std::string toPrettyString(uint32_t indent = 0);
@ -357,7 +357,7 @@ namespace MP4{
class VMHD : public fullBox{ class VMHD : public fullBox{
public: public:
VMHD(); VMHD(uint32_t version = 0, uint32_t flags = 0);
void setGraphicsMode(uint16_t newGraphicsMode); void setGraphicsMode(uint16_t newGraphicsMode);
uint16_t getGraphicsMode(); uint16_t getGraphicsMode();
uint32_t getOpColorCount(); uint32_t getOpColorCount();
@ -492,8 +492,9 @@ namespace MP4{
class TKHD : public fullBox{ class TKHD : public fullBox{
public: public:
TKHD(const Box &rs) : fullBox(rs){}
TKHD(uint32_t trackId = 0, uint64_t duration = 0, uint32_t width = 0, uint32_t height = 0); TKHD(uint32_t trackId = 0, uint64_t duration = 0, uint32_t width = 0, uint32_t height = 0);
TKHD(DTSC::Track &track, bool fragmented); TKHD(const DTSC::Meta &M, size_t idx);
void setCreationTime(uint64_t newCreationTime); void setCreationTime(uint64_t newCreationTime);
uint64_t getCreationTime(); uint64_t getCreationTime();
@ -527,7 +528,7 @@ namespace MP4{
class MDHD : public fullBox{ class MDHD : public fullBox{
public: public:
MDHD(uint64_t duration = 0); MDHD(uint64_t duration = 0, const std::string &language = "");
void setCreationTime(uint64_t newCreationTime); void setCreationTime(uint64_t newCreationTime);
uint64_t getCreationTime(); uint64_t getCreationTime();
void setModificationTime(uint64_t newModificationTime); void setModificationTime(uint64_t newModificationTime);
@ -593,6 +594,7 @@ namespace MP4{
class STCO : public fullBox{ class STCO : public fullBox{
public: public:
STCO(const Box &rs) : fullBox(rs){}
STCO(char v = 1, uint32_t f = 0); STCO(char v = 1, uint32_t f = 0);
void setEntryCount(uint32_t newEntryCount); void setEntryCount(uint32_t newEntryCount);
uint32_t getEntryCount(); uint32_t getEntryCount();
@ -604,6 +606,7 @@ namespace MP4{
class CO64 : public fullBox{ class CO64 : public fullBox{
public: public:
CO64(char v = 1, uint32_t f = 0); CO64(char v = 1, uint32_t f = 0);
CO64(const Box &rs) : fullBox(rs){}
void setEntryCount(uint32_t newEntryCount); void setEntryCount(uint32_t newEntryCount);
uint32_t getEntryCount(); uint32_t getEntryCount();
void setChunkOffset(uint64_t newChunkOffset, uint32_t no); void setChunkOffset(uint64_t newChunkOffset, uint32_t no);
@ -667,9 +670,10 @@ namespace MP4{
///\todo set default values ///\todo set default values
public: public:
VisualSampleEntry(); VisualSampleEntry();
VisualSampleEntry(DTSC::Track &track); VisualSampleEntry(const DTSC::Meta &M, size_t idx);
void initialize(); void initialize();
void setCodec(const char *newCodec); void setCodec(const char *newCodec);
std::string getCodec();
void setWidth(uint16_t newWidth); void setWidth(uint16_t newWidth);
uint16_t getWidth(); uint16_t getWidth();
void setHeight(uint16_t newHeight); void setHeight(uint16_t newHeight);
@ -700,7 +704,7 @@ namespace MP4{
public: public:
///\todo set default values ///\todo set default values
AudioSampleEntry(); AudioSampleEntry();
AudioSampleEntry(DTSC::Track &track); AudioSampleEntry(const DTSC::Meta &M, size_t idx);
void initialize(); void initialize();
void setCodec(const char *newCodec); void setCodec(const char *newCodec);
void setChannelCount(uint16_t newChannelCount); void setChannelCount(uint16_t newChannelCount);
@ -761,7 +765,7 @@ namespace MP4{
class TextSampleEntry : public SampleEntry{ class TextSampleEntry : public SampleEntry{
public: public:
TextSampleEntry(); TextSampleEntry();
TextSampleEntry(DTSC::Track &track); TextSampleEntry(const DTSC::Meta &m, size_t idx);
void initialize(); void initialize();
void setHzJustification(int8_t n); void setHzJustification(int8_t n);
void setVtJustification(int8_t n); void setVtJustification(int8_t n);

View file

@ -13,7 +13,7 @@ namespace Mpeg{
// samplerate is encoded in bits 0x0C of header[2]; // samplerate is encoded in bits 0x0C of header[2];
res.sampleRate = sampleRates[mpegVersion][((hdr[2] >> 2) & 0x03)] * 1000; res.sampleRate = sampleRates[mpegVersion][((hdr[2] >> 2) & 0x03)] * 1000;
res.channels = 2 - (hdr[3] >> 7); res.channels = 2 - (hdr[3] >> 7);
res.layer = 4 - (hdr[1] >> 1) & 0x03; res.layer = 4 - ((hdr[1] >> 1) & 0x03);
return res; return res;
} }

View file

@ -25,36 +25,36 @@ namespace RTP{
char *Packet::getPayload() const{return data + getHsize();} char *Packet::getPayload() const{return data + getHsize();}
unsigned int Packet::getVersion() const{return (data[0] >> 6) & 0x3;} uint32_t Packet::getVersion() const{return (data[0] >> 6) & 0x3;}
unsigned int Packet::getPadding() const{return (data[0] >> 5) & 0x1;} uint32_t Packet::getPadding() const{return (data[0] >> 5) & 0x1;}
unsigned int Packet::getExtension() const{return (data[0] >> 4) & 0x1;} uint32_t Packet::getExtension() const{return (data[0] >> 4) & 0x1;}
unsigned int Packet::getContribCount() const{return (data[0]) & 0xE;} uint32_t Packet::getContribCount() const{return (data[0]) & 0xE;}
unsigned int Packet::getMarker() const{return (data[1] >> 7) & 0x1;} uint32_t Packet::getMarker() const{return (data[1] >> 7) & 0x1;}
unsigned int Packet::getPayloadType() const{return (data[1]) & 0x7F;} uint32_t Packet::getPayloadType() const{return (data[1]) & 0x7F;}
unsigned int Packet::getSequence() const{return (((((unsigned int)data[2]) << 8) + data[3]));} uint16_t Packet::getSequence() const{return Bit::btohs(data + 2);}
uint32_t Packet::getTimeStamp() const{return Bit::btohl(data + 4);} uint32_t Packet::getTimeStamp() const{return Bit::btohl(data + 4);}
unsigned int Packet::getSSRC() const{return ntohl(*((unsigned int *)(data + 8)));} unsigned int Packet::getSSRC() const{return Bit::btohl(data + 8);}
char *Packet::getData(){return data + 8 + 4 * getContribCount() + getExtension();} const char *Packet::getData(){return data + 8 + 4 * getContribCount() + getExtension();}
void Packet::setTimestamp(uint32_t t){Bit::htobl(data + 4, t);} void Packet::setTimestamp(uint32_t timestamp){Bit::htobl(data + 4, timestamp);}
void Packet::setSequence(unsigned int seq){*((short *)(data + 2)) = htons(seq);} void Packet::setSequence(uint16_t seq){Bit::htobs(data + 2, seq);}
void Packet::setSSRC(unsigned long ssrc){*((int *)(data + 8)) = htonl(ssrc);} void Packet::setSSRC(uint32_t ssrc){Bit::htobl(data + 8, ssrc);}
void Packet::increaseSequence(){*((short *)(data + 2)) = htons(getSequence() + 1);} void Packet::increaseSequence(){setSequence(getSequence() + 1);}
void Packet::sendH264(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void Packet::sendH264(void *socket, void callBack(void *, const char *, size_t, uint8_t),
const char *payload, unsigned int payloadlen, unsigned int channel, bool lastOfAccesUnit){ const char *payload, uint32_t payloadlen, uint32_t channel, bool lastOfAccesUnit){
if ((payload[0] & 0x1F) == 12){return;} if ((payload[0] & 0x1F) == 12){return;}
/// \todo This function probably belongs in DMS somewhere. /// \todo This function probably belongs in DMS somewhere.
if (payloadlen + getHsize() + 2 <= maxDataLen){ if (payloadlen + getHsize() + 2 <= maxDataLen){
@ -102,7 +102,7 @@ namespace RTP{
} }
} }
void Packet::sendVP8(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void Packet::sendVP8(void *socket, void callBack(void *, const char *, size_t, uint8_t),
const char *payload, unsigned int payloadlen, unsigned int channel){ const char *payload, unsigned int payloadlen, unsigned int channel){
bool isKeyframe = ((payload[0] & 0x01) == 0) ? true : false; bool isKeyframe = ((payload[0] & 0x01) == 0) ? true : false;
@ -133,7 +133,7 @@ namespace RTP{
// WARN_MSG("KEYFRAME: %c", (isKeyframe) ? 'y' : 'n'); // WARN_MSG("KEYFRAME: %c", (isKeyframe) ? 'y' : 'n');
} }
void Packet::sendH265(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void Packet::sendH265(void *socket, void callBack(void *, const char *, size_t, uint8_t),
const char *payload, unsigned int payloadlen, unsigned int channel){ const char *payload, unsigned int payloadlen, unsigned int channel){
/// \todo This function probably belongs in DMS somewhere. /// \todo This function probably belongs in DMS somewhere.
if (payloadlen + getHsize() + 3 <= maxDataLen){ if (payloadlen + getHsize() + 3 <= maxDataLen){
@ -175,7 +175,7 @@ namespace RTP{
} }
} }
void Packet::sendMPEG2(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void Packet::sendMPEG2(void *socket, void callBack(void *, const char *, size_t, uint8_t),
const char *payload, unsigned int payloadlen, unsigned int channel){ const char *payload, unsigned int payloadlen, unsigned int channel){
/// \todo This function probably belongs in DMS somewhere. /// \todo This function probably belongs in DMS somewhere.
if (payloadlen + getHsize() + 4 <= maxDataLen){ if (payloadlen + getHsize() + 4 <= maxDataLen){
@ -223,8 +223,8 @@ namespace RTP{
} }
} }
void Packet::sendData(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void Packet::sendData(void *socket, void callBack(void *, const char *, size_t, uint8_t), const char *payload,
const char *payload, unsigned int payloadlen, unsigned int channel, std::string codec){ unsigned int payloadlen, unsigned int channel, std::string codec){
if (codec == "H264"){ if (codec == "H264"){
unsigned long sent = 0; unsigned long sent = 0;
while (sent < payloadlen){ while (sent < payloadlen){
@ -254,18 +254,18 @@ namespace RTP{
} }
/// \todo This function probably belongs in DMS somewhere. /// \todo This function probably belongs in DMS somewhere.
data[1] |= 0x80; // setting the RTP marker bit to 1 data[1] |= 0x80; // setting the RTP marker bit to 1
long offsetLen = 0; size_t offsetLen = 0;
if (codec == "AAC"){ if (codec == "AAC"){
*((long *)(data + getHsize())) = htonl(((payloadlen << 3) & 0x0010fff8) | 0x00100000); Bit::htobl(data + getHsize(), ((payloadlen << 3) & 0x0010fff8) | 0x00100000);
offsetLen = 4; offsetLen = 4;
}else if (codec == "MP3" || codec == "MP2"){ }else if (codec == "MP3" || codec == "MP2"){
// See RFC 2250, "MPEG Audio-specific header" // See RFC 2250, "MPEG Audio-specific header"
*((long *)(data + getHsize())) = 0; // this is MBZ and Frag_Offset, which are always 0 Bit::htobl(data + getHsize(), 0); // this is MBZ and Frag_Offset, which are always 0
if (payload[0] != 0xFF){FAIL_MSG("MP2/MP3 data does not start with header?");} if (payload[0] != 0xFF){FAIL_MSG("MP2/MP3 data does not start with header?");}
offsetLen = 4; offsetLen = 4;
}else if (codec == "AC3"){ }else if (codec == "AC3"){
*((short *)(data + getHsize())) = htons(0x0001); // this is 6 bits MBZ, 2 bits FT = 0 = full Bit::htobs(data + getHsize(),
// frames and 8 bits saying we send 1 frame 1); // this is 6 bits MBZ, 2 bits FT = 0 = full frames and 8 bits saying we send 1 frame
offsetLen = 2; offsetLen = 2;
} }
if (maxDataLen < getHsize() + offsetLen + payloadlen){ if (maxDataLen < getHsize() + offsetLen + payloadlen){
@ -289,8 +289,7 @@ namespace RTP{
increaseSequence(); increaseSequence();
} }
void Packet::sendRTCP_SR(long long &connectedAt, void *socket, unsigned int tid, DTSC::Meta &metadata, void Packet::sendRTCP_SR(void *socket, void callBack(void *, const char *, size_t, uint8_t)){
void callBack(void *, char *, unsigned int, unsigned int)){
char *rtcpData = (char *)malloc(32); char *rtcpData = (char *)malloc(32);
if (!rtcpData){ if (!rtcpData){
FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up."); FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up.");
@ -311,8 +310,7 @@ namespace RTP{
free(rtcpData); free(rtcpData);
} }
void Packet::sendRTCP_RR(long long &connectedAt, SDP::Track &sTrk, unsigned int tid, DTSC::Meta &metadata, void Packet::sendRTCP_RR(SDP::Track &sTrk, void callBack(void *, const char *, size_t, uint8_t)){
void callBack(void *, char *, unsigned int, unsigned int)){
char *rtcpData = (char *)malloc(32); char *rtcpData = (char *)malloc(32);
if (!rtcpData){ if (!rtcpData){
FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up."); FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up.");
@ -330,7 +328,7 @@ namespace RTP{
Bit::htobl(rtcpData + 20, 0); /// \TODO jitter (diff in timestamp vs packet arrival) Bit::htobl(rtcpData + 20, 0); /// \TODO jitter (diff in timestamp vs packet arrival)
Bit::htobl(rtcpData + 24, 0); /// \TODO last SR (middle 32 bits of last SR or zero) Bit::htobl(rtcpData + 24, 0); /// \TODO last SR (middle 32 bits of last SR or zero)
Bit::htobl(rtcpData + 28, 0); /// \TODO delay since last SR in 2b seconds + 2b fraction Bit::htobl(rtcpData + 28, 0); /// \TODO delay since last SR in 2b seconds + 2b fraction
callBack(&(sTrk.rtcp), (char *)rtcpData, 32, 0); callBack(&(sTrk.rtcp), rtcpData, 32, 0);
sTrk.sorter.lostCurrent = 0; sTrk.sorter.lostCurrent = 0;
sTrk.sorter.packCurrent = 0; sTrk.sorter.packCurrent = 0;
free(rtcpData); free(rtcpData);
@ -344,8 +342,7 @@ namespace RTP{
sentPackets = 0; sentPackets = 0;
} }
Packet::Packet(unsigned int payloadType, unsigned int sequence, unsigned int timestamp, Packet::Packet(uint32_t payloadType, uint32_t sequence, uint64_t timestamp, uint32_t ssrc, uint32_t csrcCount){
unsigned int ssrc, unsigned int csrcCount){
managed = true; managed = true;
data = new char[12 + 4 * csrcCount + 2 + MAX_SEND]; // headerSize, 2 for FU-A, MAX_SEND for maximum sent size data = new char[12 + 4 * csrcCount + 2 + MAX_SEND]; // headerSize, 2 for FU-A, MAX_SEND for maximum sent size
if (data){ if (data){
@ -409,7 +406,7 @@ namespace RTP{
Packet::~Packet(){ Packet::~Packet(){
if (managed){delete[] data;} if (managed){delete[] data;}
} }
Packet::Packet(const char *dat, unsigned int len){ Packet::Packet(const char *dat, uint64_t len){
managed = false; managed = false;
maxDataLen = len; maxDataLen = len;
sentBytes = 0; sentBytes = 0;
@ -496,7 +493,7 @@ namespace RTP{
while (packBuffer.count(rtpSeq)){ while (packBuffer.count(rtpSeq)){
outPacket(packTrack, packBuffer[rtpSeq]); outPacket(packTrack, packBuffer[rtpSeq]);
packBuffer.erase(rtpSeq); packBuffer.erase(rtpSeq);
VERYHIGH_MSG("Sent packet %u, now %llu in buffer", rtpSeq, packBuffer.size()); INFO_MSG("Sent packet %u, now %zu in buffer", rtpSeq, packBuffer.size());
++rtpSeq; ++rtpSeq;
++packTotal; ++packTotal;
++packCurrent; ++packCurrent;
@ -506,7 +503,7 @@ namespace RTP{
while (packBuffer.count(rtpSeq)){ while (packBuffer.count(rtpSeq)){
outPacket(packTrack, packBuffer[rtpSeq]); outPacket(packTrack, packBuffer[rtpSeq]);
packBuffer.erase(rtpSeq); packBuffer.erase(rtpSeq);
VERYHIGH_MSG("Sent packet %u, now %llu in buffer", rtpSeq, packBuffer.size()); INFO_MSG("Sent packet %u, now %zu in buffer", rtpSeq, packBuffer.size());
++rtpSeq; ++rtpSeq;
++packTotal; ++packTotal;
++packCurrent; ++packCurrent;
@ -542,7 +539,7 @@ namespace RTP{
cbPack = 0; cbPack = 0;
cbInit = 0; cbInit = 0;
multiplier = 1.0; multiplier = 1.0;
trackId = 0; trackId = INVALID_TRACK_ID;
firstTime = 0; firstTime = 0;
packCount = 0; packCount = 0;
lastSeq = 0; lastSeq = 0;
@ -572,10 +569,12 @@ namespace RTP{
} }
} }
void toDTSC::setProperties(const DTSC::Track &Trk){ void toDTSC::setProperties(const DTSC::Meta &M, size_t tid){
double m = (double)Trk.rate / 1000.0; double m = (double)M.getRate(tid) / 1000.0;
if (Trk.type == "video" || Trk.codec == "MP2" || Trk.codec == "MP3"){m = 90.0;} if (M.getType(tid) == "video" || M.getCodec(tid) == "MP2" || M.getCodec(tid) == "MP3"){
setProperties(Trk.trackID, Trk.codec, Trk.type, Trk.init, m); m = 90.0;
}
setProperties(tid, M.getCodec(tid), M.getType(tid), M.getInit(tid), m);
} }
void toDTSC::setCallbacks(void (*cbP)(const DTSC::Packet &pkt), void toDTSC::setCallbacks(void (*cbP)(const DTSC::Packet &pkt),
@ -610,11 +609,12 @@ namespace RTP{
} }
prevTime = pkt.getTimeStamp(); prevTime = pkt.getTimeStamp();
uint64_t msTime = ((uint64_t)pTime - firstTime + 1 + 0xFFFFFFFFull * wrapArounds) / multiplier; uint64_t msTime = ((uint64_t)pTime - firstTime + 1 + 0xFFFFFFFFull * wrapArounds) / multiplier;
char *pl = pkt.getPayload(); char *pl = (char *)pkt.getPayload();
uint32_t plSize = pkt.getPayloadSize(); uint32_t plSize = pkt.getPayloadSize();
bool missed = lastSeq != (pkt.getSequence() - 1); bool missed = lastSeq != (pkt.getSequence() - 1);
lastSeq = pkt.getSequence(); lastSeq = pkt.getSequence();
INSANE_MSG("Received RTP packet for track %llu, time %llu -> %llu", trackId, pkt.getTimeStamp(), msTime); INSANE_MSG("Received RTP packet for track %" PRIu64 ", time %" PRIu32 " -> %" PRIu64, trackId,
pkt.getTimeStamp(), msTime);
// From here on, there is codec-specific parsing. We call handler functions for each codec, // From here on, there is codec-specific parsing. We call handler functions for each codec,
// except for the trivial codecs. // except for the trivial codecs.
if (codec == "H264"){ if (codec == "H264"){
@ -749,7 +749,7 @@ namespace RTP{
} }
void toDTSC::handleHEVCSingle(uint64_t ts, const char *buffer, const uint32_t len, bool isKey){ void toDTSC::handleHEVCSingle(uint64_t ts, const char *buffer, const uint32_t len, bool isKey){
MEDIUM_MSG("H265: %llu@%llu, %lub%s", trackId, ts, len, isKey ? " (key)" : ""); MEDIUM_MSG("H265: %" PRIu64 "@%" PRIu64 ", %" PRIu32 "b%s", trackId, ts, len, isKey ? " (key)" : "");
// Ignore zero-length packets (e.g. only contained init data and nothing else) // Ignore zero-length packets (e.g. only contained init data and nothing else)
if (!len){return;} if (!len){return;}
@ -787,11 +787,13 @@ namespace RTP{
offset = (frameNo - packCount) * (1000.0 / fps); offset = (frameNo - packCount) * (1000.0 / fps);
//... and the timestamp is the packet counter times the frame rate in ms. //... and the timestamp is the packet counter times the frame rate in ms.
newTs = packCount * (1000.0 / fps); newTs = packCount * (1000.0 / fps);
VERYHIGH_MSG("Packing time %llu = %sframe %llu (%.2f FPS). Expected %llu -> +%llu/%lu", ts, VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (%.2f FPS). Expected %" PRIu64
isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset); " -> +%" PRIu64 "/%" PRIu32,
ts, (isKey ? "key" : "i"), frameNo, fps, packCount, (frameNo - packCount), offset);
}else{ }else{
// For non-steady frame rate, assume no offsets are used and the timestamp is already correct // For non-steady frame rate, assume no offsets are used and the timestamp is already correct
VERYHIGH_MSG("Packing time %llu = %sframe %llu (variable rate)", ts, isKey ? "key" : "i", packCount); VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (variable rate)", ts,
isKey ? "key" : "i", packCount);
} }
// Fill the new DTSC packet, buffer it. // Fill the new DTSC packet, buffer it.
DTSC::Packet nextPack; DTSC::Packet nextPack;
@ -893,7 +895,7 @@ namespace RTP{
} }
void toDTSC::handleH264Single(uint64_t ts, const char *buffer, const uint32_t len, bool isKey){ void toDTSC::handleH264Single(uint64_t ts, const char *buffer, const uint32_t len, bool isKey){
MEDIUM_MSG("H264: %llu@%llu, %lub%s", trackId, ts, len, isKey ? " (key)" : ""); MEDIUM_MSG("H264: %" PRIu64 "@%" PRIu64 ", %" PRIu32 "b%s", trackId, ts, len, isKey ? " (key)" : "");
// Ignore zero-length packets (e.g. only contained init data and nothing else) // Ignore zero-length packets (e.g. only contained init data and nothing else)
if (!len){return;} if (!len){return;}
@ -976,12 +978,14 @@ namespace RTP{
offset = (frameNo - packCount) * (1000.0 / fps); offset = (frameNo - packCount) * (1000.0 / fps);
//... and the timestamp is the packet counter times the frame rate in ms. //... and the timestamp is the packet counter times the frame rate in ms.
newTs = packCount * (1000.0 / fps); newTs = packCount * (1000.0 / fps);
VERYHIGH_MSG("Packing time %llu = %sframe %llu (%.2f FPS). Expected %llu -> +%llu/%lu", ts, VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (%.2f FPS). Expected %" PRIu64
isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset); " -> +%" PRIu64 "/%" PRIu32,
ts, isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset);
}else{ }else{
// For non-steady frame rate, assume no offsets are used and the timestamp is already // For non-steady frame rate, assume no offsets are used and the timestamp is already
// correct // correct
VERYHIGH_MSG("Packing time %llu = %sframe %llu (variable rate)", ts, isKey ? "key" : "i", packCount); VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (variable rate)", ts,
isKey ? "key" : "i", packCount);
} }
// Fill the new DTSC packet, buffer it. // Fill the new DTSC packet, buffer it.
DTSC::Packet nextPack; DTSC::Packet nextPack;
@ -1007,11 +1011,13 @@ namespace RTP{
offset = (frameNo - packCount) * (1000.0 / fps); offset = (frameNo - packCount) * (1000.0 / fps);
//... and the timestamp is the packet counter times the frame rate in ms. //... and the timestamp is the packet counter times the frame rate in ms.
newTs = packCount * (1000.0 / fps); newTs = packCount * (1000.0 / fps);
VERYHIGH_MSG("Packing time %llu = %sframe %llu (%.2f FPS). Expected %llu -> +%llu/%lu", ts, VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (%.2f FPS). Expected %" PRIu64
isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset); " -> +%" PRIu64 "/%" PRIu32,
ts, isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset);
}else{ }else{
// For non-steady frame rate, assume no offsets are used and the timestamp is already correct // For non-steady frame rate, assume no offsets are used and the timestamp is already correct
VERYHIGH_MSG("Packing time %llu = %sframe %llu (variable rate)", ts, isKey ? "key" : "i", packCount); VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (variable rate)", ts,
isKey ? "key" : "i", packCount);
} }
// Fill the new DTSC packet, buffer it. // Fill the new DTSC packet, buffer it.
DTSC::Packet nextPack; DTSC::Packet nextPack;

View file

@ -25,7 +25,7 @@ namespace SDP{
/// This namespace holds all RTP-parsing and sending related functionality. /// This namespace holds all RTP-parsing and sending related functionality.
namespace RTP{ namespace RTP{
extern unsigned int MAX_SEND; extern uint32_t MAX_SEND;
/// This class is used to make RTP packets. Currently, H264, and AAC are supported. RTP /// This class is used to make RTP packets. Currently, H264, and AAC are supported. RTP
/// mechanisms, like increasing sequence numbers and setting timestamps are all taken care of in /// mechanisms, like increasing sequence numbers and setting timestamps are all taken care of in
@ -35,49 +35,47 @@ namespace RTP{
bool managed; bool managed;
char *data; ///< The actual RTP packet that is being sent char *data; ///< The actual RTP packet that is being sent
uint32_t maxDataLen; ///< Amount of reserved bytes for the packet(s) uint32_t maxDataLen; ///< Amount of reserved bytes for the packet(s)
int sentPackets; uint32_t sentPackets;
int sentBytes; // Because ugly is beautiful uint32_t sentBytes; // Because ugly is beautiful
public: public:
static double startRTCP; static double startRTCP;
unsigned int getHsize() const; uint32_t getHsize() const;
unsigned int getPayloadSize() const; uint32_t getPayloadSize() const;
char *getPayload() const; char *getPayload() const;
unsigned int getVersion() const; uint32_t getVersion() const;
unsigned int getPadding() const; uint32_t getPadding() const;
unsigned int getExtension() const; uint32_t getExtension() const;
unsigned int getContribCount() const; uint32_t getContribCount() const;
unsigned int getMarker() const; uint32_t getMarker() const;
unsigned int getPayloadType() const; uint32_t getPayloadType() const;
unsigned int getSequence() const; uint16_t getSequence() const;
uint32_t getTimeStamp() const; uint32_t getTimeStamp() const;
void setSequence(unsigned int seq); void setSequence(uint16_t seq);
unsigned int getSSRC() const; uint32_t getSSRC() const;
void setSSRC(unsigned long ssrc); void setSSRC(uint32_t ssrc);
void setTimestamp(uint32_t t); void setTimestamp(uint32_t t);
void increaseSequence(); void increaseSequence();
void sendH264(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void sendH264(void *socket, void callBack(void *, const char *, size_t, uint8_t), const char *payload,
const char *payload, unsigned int payloadlen, unsigned int channel, bool lastOfAccesUnit); unsigned int payloadlen, unsigned int channel, bool lastOfAccessUnit);
void sendVP8(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void sendVP8(void *socket, void callBack(void *, const char *, size_t, uint8_t),
const char *payload, unsigned int payloadlen, unsigned int channel); const char *payload, unsigned int payloadlen, unsigned int channel);
void sendH265(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void sendH265(void *socket, void callBack(void *, const char *, size_t, uint8_t),
const char *payload, unsigned int payloadlen, unsigned int channel); const char *payload, unsigned int payloadlen, unsigned int channel);
void sendMPEG2(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void sendMPEG2(void *socket, void callBack(void *, const char *, size_t, uint8_t),
const char *payload, unsigned int payloadlen, unsigned int channel); const char *payload, unsigned int payloadlen, unsigned int channel);
void sendData(void *socket, void callBack(void *, char *, unsigned int, unsigned int), void sendData(void *socket, void callBack(void *, const char *, size_t, uint8_t), const char *payload,
const char *payload, unsigned int payloadlen, unsigned int channel, std::string codec); unsigned int payloadlen, unsigned int channel, std::string codec);
void sendRTCP_SR(long long &connectedAt, void *socket, unsigned int tid, DTSC::Meta &metadata, void sendRTCP_SR(void *socket, void callBack(void *, const char *, size_t, uint8_t));
void callBack(void *, char *, unsigned int, unsigned int)); void sendRTCP_RR(SDP::Track &sTrk, void callBack(void *, const char *, size_t, uint8_t));
void sendRTCP_RR(long long &connectedAt, SDP::Track &sTrk, unsigned int tid,
DTSC::Meta &metadata, void callBack(void *, char *, unsigned int, unsigned int));
Packet(); Packet();
Packet(unsigned int pt, unsigned int seq, unsigned int ts, unsigned int ssr, unsigned int csrcCount = 0); Packet(uint32_t pt, uint32_t seq, uint64_t ts, uint32_t ssr, uint32_t csrcCount = 0);
Packet(const Packet &o); Packet(const Packet &o);
void operator=(const Packet &o); void operator=(const Packet &o);
~Packet(); ~Packet();
Packet(const char *dat, unsigned int len); Packet(const char *dat, uint64_t len);
char *getData(); const char *getData();
char *ptr() const{return data;} char *ptr() const{return data;}
}; };
@ -130,7 +128,7 @@ namespace RTP{
toDTSC(); toDTSC();
void setProperties(const uint64_t track, const std::string &codec, const std::string &type, void setProperties(const uint64_t track, const std::string &codec, const std::string &type,
const std::string &init, const double multiplier); const std::string &init, const double multiplier);
void setProperties(const DTSC::Track &Trk); void setProperties(const DTSC::Meta &M, size_t tid);
void setCallbacks(void (*cbPack)(const DTSC::Packet &pkt), void setCallbacks(void (*cbPack)(const DTSC::Packet &pkt),
void (*cbInit)(const uint64_t track, const std::string &initData)); void (*cbInit)(const uint64_t track, const std::string &initData));
void addRTP(const RTP::Packet &rPkt); void addRTP(const RTP::Packet &rPkt);

View file

@ -240,7 +240,7 @@ namespace RTP{
size_t maskNumBytes = getNumBytesUsedForMask(); size_t maskNumBytes = getNumBytesUsedForMask();
if (maskNumBytes != 2 && maskNumBytes != 6){ if (maskNumBytes != 2 && maskNumBytes != 6){
FAIL_MSG("Invalid mask size (%u) cannot extract sequence numbers.", maskNumBytes); FAIL_MSG("Invalid mask size (%zu) cannot extract sequence numbers.", maskNumBytes);
return false; return false;
} }
@ -259,7 +259,7 @@ namespace RTP{
for (uint16_t byteDX = 0; byteDX < maskNumBytes; ++byteDX){ for (uint16_t byteDX = 0; byteDX < maskNumBytes; ++byteDX){
uint8_t maskByte = maskPtr[byteDX]; uint8_t maskByte = maskPtr[byteDX];
for (uint16_t bitDX = 0; bitDX < 8; ++bitDX){ for (uint16_t bitDX = 0; bitDX < 8; ++bitDX){
if (maskByte & (1 << 7 - bitDX)){ if (maskByte & ((1 << 7) - bitDX)){
uint16_t seqNum = seqNumBase + (byteDX << 3) + bitDX; uint16_t seqNum = seqNumBase + (byteDX << 3) + bitDX;
coveredSeqNums.insert(seqNum); coveredSeqNums.insert(seqNum);
} }
@ -502,7 +502,7 @@ namespace RTP{
Packet recreatedPacket; Packet recreatedPacket;
fec->tryToRecoverMissingPacket(packetHistory, recreatedPacket); fec->tryToRecoverMissingPacket(packetHistory, recreatedPacket);
if (recreatedPacket.ptr() != NULL){ if (recreatedPacket.ptr() != NULL){
char *pl = recreatedPacket.getPayload(); char *pl = (char *)recreatedPacket.getPayload();
WARN_MSG(" => reconstructed %u, %02X %02X %02X %02X | %02X %02X %02X %02X", WARN_MSG(" => reconstructed %u, %02X %02X %02X %02X | %02X %02X %02X %02X",
recreatedPacket.getSequence(), pl[0], pl[1], pl[2], pl[3], pl[4], pl[5], pl[6], pl[7]); recreatedPacket.getSequence(), pl[0], pl[1], pl[2], pl[3], pl[4], pl[5], pl[6], pl[7]);
addPacket(recreatedPacket); addPacket(recreatedPacket);
@ -531,7 +531,7 @@ namespace RTP{
} }
void FECPacket::sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void *userData, void FECPacket::sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void *userData,
void callBack(void *userData, const char *payload, uint32_t nbytes)){ void callBack(void *userData, const char *payload, size_t nbytes, uint8_t channel)){
char *rtcpData = (char *)malloc(32); char *rtcpData = (char *)malloc(32);
if (!rtcpData){ if (!rtcpData){
FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up."); FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up.");
@ -545,11 +545,12 @@ namespace RTP{
Bit::htobl(rtcpData + 8, theirSSRC); // set source identifier Bit::htobl(rtcpData + 8, theirSSRC); // set source identifier
rtcpData[12] = (sorter.lostCurrent * 255) / (sorter.lostCurrent + sorter.packCurrent); // fraction lost since prev RR rtcpData[12] = (sorter.lostCurrent * 255) / (sorter.lostCurrent + sorter.packCurrent); // fraction lost since prev RR
Bit::htob24(rtcpData + 13, sorter.lostTotal); // cumulative packets lost since start Bit::htob24(rtcpData + 13, sorter.lostTotal); // cumulative packets lost since start
Bit::htobl(rtcpData + 16, sorter.rtpSeq | (sorter.packTotal & 0xFFFF0000ul)); // highest sequence received Bit::htobl(rtcpData + 16,
sorter.rtpSeq | (sorter.packTotal & 0xFFFF0000ul)); // highest sequence received
Bit::htobl(rtcpData + 20, 0); /// \TODO jitter (diff in timestamp vs packet arrival) Bit::htobl(rtcpData + 20, 0); /// \TODO jitter (diff in timestamp vs packet arrival)
Bit::htobl(rtcpData + 24, 0); /// \TODO last SR (middle 32 bits of last SR or zero) Bit::htobl(rtcpData + 24, 0); /// \TODO last SR (middle 32 bits of last SR or zero)
Bit::htobl(rtcpData + 28, 0); /// \TODO delay since last SR in 2b seconds + 2b fraction Bit::htobl(rtcpData + 28, 0); /// \TODO delay since last SR in 2b seconds + 2b fraction
callBack(userData, rtcpData, 32); callBack(userData, rtcpData, 32, 0);
sorter.lostCurrent = 0; sorter.lostCurrent = 0;
sorter.packCurrent = 0; sorter.packCurrent = 0;
free(rtcpData); free(rtcpData);

View file

@ -86,7 +86,7 @@ namespace RTP{
class FECPacket : public Packet{ class FECPacket : public Packet{
public: public:
void sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void *userData, void sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void *userData,
void callBack(void *userData, const char *payload, uint32_t nbytes)); void callBack(void *userData, const char *payload, size_t nbytes, uint8_t channel));
}; };
}// namespace RTP }// namespace RTP

View file

@ -45,24 +45,26 @@ namespace SDP{
} }
/// Gets the SDP contents for sending out a particular given DTSC::Track. /// Gets the SDP contents for sending out a particular given DTSC::Track.
std::string mediaDescription(const DTSC::Track &trk){ std::string mediaDescription(const DTSC::Meta *meta, size_t tid){
const DTSC::Meta &M = *meta;
std::stringstream mediaDesc; std::stringstream mediaDesc;
if (trk.codec == "H264"){
std::string codec = M.getCodec(tid);
std::string init = M.getInit(tid);
if (codec == "H264"){
MP4::AVCC avccbox; MP4::AVCC avccbox;
avccbox.setPayload(trk.init); avccbox.setPayload(init);
mediaDesc << "m=video 0 RTP/AVP 97\r\n" mediaDesc << "m=video 0 RTP/AVP 97\r\n"
"a=rtpmap:97 H264/90000\r\n" "a=rtpmap:97 H264/90000\r\n"
"a=cliprect:0,0," "a=cliprect:0,0,"
<< trk.height << "," << trk.width << M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:97 "
<< "\r\n" << M.getWidth(tid) << '-' << M.getHeight(tid)
"a=framesize:97 "
<< trk.width << '-' << trk.height
<< "\r\n" << "\r\n"
"a=fmtp:97 packetization-mode=1;profile-level-id=" "a=fmtp:97 packetization-mode=1;profile-level-id="
<< std::hex << std::setw(2) << std::setfill('0') << (int)trk.init.data()[1] << std::hex << std::setw(2) << std::setfill('0') << (int)init.data()[1] << std::dec
<< std::dec << "E0" << std::hex << std::setw(2) << std::setfill('0') << "E0" << std::hex << std::setw(2) << std::setfill('0') << (int)init.data()[3]
<< (int)trk.init.data()[3] << std::dec << ";" << std::dec << ";sprop-parameter-sets=";
<< "sprop-parameter-sets=";
size_t count = avccbox.getSPSCount(); size_t count = avccbox.getSPSCount();
for (size_t i = 0; i < count; ++i){ for (size_t i = 0; i < count; ++i){
mediaDesc << (i ? "," : "") mediaDesc << (i ? "," : "")
@ -75,19 +77,15 @@ namespace SDP{
<< Encodings::Base64::encode(std::string(avccbox.getPPS(i), avccbox.getPPSLen(i))); << Encodings::Base64::encode(std::string(avccbox.getPPS(i), avccbox.getPPSLen(i)));
} }
mediaDesc << "\r\n" mediaDesc << "\r\n"
<< "a=framerate:" << ((double)trk.fpks) / 1000.0 "a=framerate:"
<< "\r\n" << ((double)M.getFpks(tid)) / 1000.0 << "\r\na=control:track" << tid << "\r\n";
"a=control:track" }else if (codec == "HEVC"){
<< trk.trackID << "\r\n"; h265::initData iData(init);
}else if (trk.codec == "HEVC"){
h265::initData iData(trk.init);
mediaDesc << "m=video 0 RTP/AVP 104\r\n" mediaDesc << "m=video 0 RTP/AVP 104\r\n"
"a=rtpmap:104 H265/90000\r\n" "a=rtpmap:104 H265/90000\r\n"
"a=cliprect:0,0," "a=cliprect:0,0,"
<< trk.height << "," << trk.width << M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:104 "
<< "\r\n" << M.getWidth(tid) << '-' << M.getHeight(tid) << "\r\n"
"a=framesize:104 "
<< trk.width << '-' << trk.height << "\r\n"
<< "a=fmtp:104 sprop-vps="; << "a=fmtp:104 sprop-vps=";
const std::set<std::string> &vps = iData.getVPS(); const std::set<std::string> &vps = iData.getVPS();
if (vps.size()){ if (vps.size()){
@ -112,90 +110,80 @@ namespace SDP{
mediaDesc << Encodings::Base64::encode(*it); mediaDesc << Encodings::Base64::encode(*it);
} }
} }
mediaDesc << "\r\na=framerate:" << ((double)trk.fpks) / 1000.0 mediaDesc << "\r\na=framerate:" << ((double)M.getFpks(tid)) / 1000.0 << "\r\na=control:track"
<< "\r\n" << tid << "\r\n";
"a=control:track" }else if (codec == "MPEG2"){
<< trk.trackID << "\r\n";
}else if (trk.codec == "MPEG2"){
mediaDesc << "m=video 0 RTP/AVP 32\r\n" mediaDesc << "m=video 0 RTP/AVP 32\r\n"
"a=cliprect:0,0," "a=cliprect:0,0,"
<< trk.height << "," << trk.width << M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:32 " << M.getWidth(tid)
<< "\r\n" << '-' << M.getHeight(tid) << "\r\na=framerate:" << ((double)M.getFpks(tid)) / 1000.0
"a=framesize:32 " << "\r\na=control:track" << tid << "\r\n";
<< trk.width << '-' << trk.height << "\r\n" }else if (codec == "AAC"){
<< "a=framerate:" << ((double)trk.fpks) / 1000.0 << "\r\n"
<< "a=control:track" << trk.trackID << "\r\n";
}else if (trk.codec == "AAC"){
mediaDesc << "m=audio 0 RTP/AVP 96" mediaDesc << "m=audio 0 RTP/AVP 96"
<< "\r\n" << "\r\n"
"a=rtpmap:96 mpeg4-generic/" "a=rtpmap:96 mpeg4-generic/"
<< trk.rate << "/" << trk.channels << M.getRate(tid) << "/" << M.getChannels(tid)
<< "\r\n" << "\r\n"
"a=fmtp:96 streamtype=5; profile-level-id=15; config="; "a=fmtp:96 streamtype=5; profile-level-id=15; config=";
for (unsigned int i = 0; i < trk.init.size(); i++){ for (unsigned int i = 0; i < init.size(); i++){
mediaDesc << std::hex << std::setw(2) << std::setfill('0') << (int)trk.init[i] << std::dec; mediaDesc << std::hex << std::setw(2) << std::setfill('0') << (int)init[i] << std::dec;
} }
// these values are described in RFC 3640 // these values are described in RFC 3640
mediaDesc << "; mode=AAC-hbr; SizeLength=13; IndexLength=3; IndexDeltaLength=3;\r\n" mediaDesc << "; mode=AAC-hbr; SizeLength=13; IndexLength=3; IndexDeltaLength=3;\r\n"
"a=control:track" "a=control:track"
<< trk.trackID << "\r\n"; << tid << "\r\n";
}else if (trk.codec == "MP3" || trk.codec == "MP2"){ }else if (codec == "MP3" || codec == "MP2"){
mediaDesc << "m=" << trk.type << " 0 RTP/AVP 14" mediaDesc << "m=" << M.getType(tid) << " 0 RTP/AVP 14"
<< "\r\n" << "\r\n"
"a=rtpmap:14 MPA/90000/" "a=rtpmap:14 MPA/90000/"
<< trk.channels << M.getChannels(tid) << "\r\n"
<< "\r\n" << "a=control:track" << tid << "\r\n";
"a=control:track" }else if (codec == "AC3"){
<< trk.trackID << "\r\n";
}else if (trk.codec == "AC3"){
mediaDesc << "m=audio 0 RTP/AVP 100" mediaDesc << "m=audio 0 RTP/AVP 100"
<< "\r\n" << "\r\n"
"a=rtpmap:100 AC3/" "a=rtpmap:100 AC3/"
<< trk.rate << "/" << trk.channels << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n"
<< "\r\n" << "a=control:track" << tid << "\r\n";
"a=control:track" }else if (codec == "ALAW"){
<< trk.trackID << "\r\n"; if (M.getChannels(tid) == 1 && M.getRate(tid) == 8000){
}else if (trk.codec == "ALAW"){
if (trk.channels == 1 && trk.rate == 8000){
mediaDesc << "m=audio 0 RTP/AVP 8" mediaDesc << "m=audio 0 RTP/AVP 8"
<< "\r\n"; << "\r\n";
}else{ }else{
mediaDesc << "m=audio 0 RTP/AVP 101" mediaDesc << "m=audio 0 RTP/AVP 101"
<< "\r\n"; << "\r\n";
mediaDesc << "a=rtpmap:101 PCMA/" << trk.rate << "/" << trk.channels << "\r\n"; mediaDesc << "a=rtpmap:101 PCMA/" << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n";
} }
mediaDesc << "a=control:track" << trk.trackID << "\r\n"; mediaDesc << "a=control:track" << tid << "\r\n";
}else if (trk.codec == "ULAW"){ }else if (codec == "ULAW"){
if (trk.channels == 1 && trk.rate == 8000){ if (M.getChannels(tid) == 1 && M.getRate(tid) == 8000){
mediaDesc << "m=audio 0 RTP/AVP 0" mediaDesc << "m=audio 0 RTP/AVP 0"
<< "\r\n"; << "\r\n";
}else{ }else{
mediaDesc << "m=audio 0 RTP/AVP 104" mediaDesc << "m=audio 0 RTP/AVP 104"
<< "\r\n"; << "\r\n";
mediaDesc << "a=rtpmap:104 PCMU/" << trk.rate << "/" << trk.channels << "\r\n"; mediaDesc << "a=rtpmap:104 PCMU/" << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n";
} }
mediaDesc << "a=control:track" << trk.trackID << "\r\n"; mediaDesc << "a=control:track" << tid << "\r\n";
}else if (trk.codec == "PCM"){ }else if (codec == "PCM"){
if (trk.size == 16 && trk.channels == 2 && trk.rate == 44100){ if (M.getSize(tid) == 16 && M.getChannels(tid) == 2 && M.getRate(tid) == 44100){
mediaDesc << "m=audio 0 RTP/AVP 10" mediaDesc << "m=audio 0 RTP/AVP 10"
<< "\r\n"; << "\r\n";
}else if (trk.size == 16 && trk.channels == 1 && trk.rate == 44100){ }else if (M.getSize(tid) == 16 && M.getChannels(tid) == 1 && M.getRate(tid) == 44100){
mediaDesc << "m=audio 0 RTP/AVP 11" mediaDesc << "m=audio 0 RTP/AVP 11"
<< "\r\n"; << "\r\n";
}else{ }else{
mediaDesc << "m=audio 0 RTP/AVP 103" mediaDesc << "m=audio 0 RTP/AVP 103"
<< "\r\n"; << "\r\n";
mediaDesc << "a=rtpmap:103 L" << trk.size << "/" << trk.rate << "/" << trk.channels << "\r\n"; mediaDesc << "a=rtpmap:103 L" << M.getSize(tid) << "/" << M.getRate(tid) << "/"
<< M.getChannels(tid) << "\r\n";
} }
mediaDesc << "a=control:track" << trk.trackID << "\r\n"; mediaDesc << "a=control:track" << tid << "\r\n";
}else if (trk.codec == "opus"){ }else if (codec == "opus"){
mediaDesc << "m=audio 0 RTP/AVP 102" mediaDesc << "m=audio 0 RTP/AVP 102"
<< "\r\n" << "\r\n"
"a=rtpmap:102 opus/" "a=rtpmap:102 opus/"
<< trk.rate << "/" << trk.channels << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n"
<< "\r\n" << "a=control:track" << tid << "\r\n";
"a=control:track"
<< trk.trackID << "\r\n";
} }
return mediaDesc.str(); return mediaDesc.str();
} }
@ -232,43 +220,44 @@ namespace SDP{
/// \source The source identifier. /// \source The source identifier.
/// \return True if successful, false otherwise. /// \return True if successful, false otherwise.
bool Track::parseTransport(const std::string &transport, const std::string &host, bool Track::parseTransport(const std::string &transport, const std::string &host,
const std::string &source, const DTSC::Track &trk){ const std::string &source, const DTSC::Meta *M, size_t tid){
if (trk.codec == "H264"){ std::string codec = M->getCodec(tid);
if (codec == "H264"){
pack = RTP::Packet(97, 1, 0, mySSRC); pack = RTP::Packet(97, 1, 0, mySSRC);
}else if (trk.codec == "HEVC"){ }else if (codec == "HEVC"){
pack = RTP::Packet(104, 1, 0, mySSRC); pack = RTP::Packet(104, 1, 0, mySSRC);
}else if (trk.codec == "MPEG2"){ }else if (codec == "MPEG2"){
pack = RTP::Packet(32, 1, 0, mySSRC); pack = RTP::Packet(32, 1, 0, mySSRC);
}else if (trk.codec == "AAC"){ }else if (codec == "AAC"){
pack = RTP::Packet(96, 1, 0, mySSRC); pack = RTP::Packet(96, 1, 0, mySSRC);
}else if (trk.codec == "AC3"){ }else if (codec == "AC3"){
pack = RTP::Packet(100, 1, 0, mySSRC); pack = RTP::Packet(100, 1, 0, mySSRC);
}else if (trk.codec == "MP3" || trk.codec == "MP2"){ }else if (codec == "MP3" || codec == "MP2"){
pack = RTP::Packet(14, 1, 0, mySSRC); pack = RTP::Packet(14, 1, 0, mySSRC);
}else if (trk.codec == "ALAW"){ }else if (codec == "ALAW"){
if (trk.channels == 1 && trk.rate == 8000){ if (M->getChannels(tid) == 1 && M->getRate(tid) == 8000){
pack = RTP::Packet(8, 1, 0, mySSRC); pack = RTP::Packet(8, 1, 0, mySSRC);
}else{ }else{
pack = RTP::Packet(101, 1, 0, mySSRC); pack = RTP::Packet(101, 1, 0, mySSRC);
} }
}else if (trk.codec == "ULAW"){ }else if (codec == "ULAW"){
if (trk.channels == 1 && trk.rate == 8000){ if (M->getChannels(tid) == 1 && M->getRate(tid) == 8000){
pack = RTP::Packet(0, 1, 0, mySSRC); pack = RTP::Packet(0, 1, 0, mySSRC);
}else{ }else{
pack = RTP::Packet(104, 1, 0, mySSRC); pack = RTP::Packet(104, 1, 0, mySSRC);
} }
}else if (trk.codec == "PCM"){ }else if (codec == "PCM"){
if (trk.size == 16 && trk.channels == 2 && trk.rate == 44100){ if (M->getSize(tid) == 16 && M->getChannels(tid) == 2 && M->getRate(tid) == 44100){
pack = RTP::Packet(10, 1, 0, mySSRC); pack = RTP::Packet(10, 1, 0, mySSRC);
}else if (trk.size == 16 && trk.channels == 1 && trk.rate == 44100){ }else if (M->getSize(tid) == 16 && M->getChannels(tid) == 1 && M->getRate(tid) == 44100){
pack = RTP::Packet(11, 1, 0, mySSRC); pack = RTP::Packet(11, 1, 0, mySSRC);
}else{ }else{
pack = RTP::Packet(103, 1, 0, mySSRC); pack = RTP::Packet(103, 1, 0, mySSRC);
} }
}else if (trk.codec == "opus"){ }else if (codec == "opus"){
pack = RTP::Packet(102, 1, 0, mySSRC); pack = RTP::Packet(102, 1, 0, mySSRC);
}else{ }else{
ERROR_MSG("Unsupported codec %s for RTSP on track %u", trk.codec.c_str(), trk.trackID); ERROR_MSG("Unsupported codec %s for RTSP on track %zu", codec.c_str(), tid);
return false; return false;
} }
if (transport.find("TCP") != std::string::npos){ if (transport.find("TCP") != std::string::npos){
@ -334,10 +323,10 @@ namespace SDP{
} }
/// Gets the rtpInfo for a given DTSC::Track, source identifier and timestamp (in millis). /// Gets the rtpInfo for a given DTSC::Track, source identifier and timestamp (in millis).
std::string Track::rtpInfo(const DTSC::Track &trk, const std::string &source, uint64_t currentTime){ std::string Track::rtpInfo(const DTSC::Meta &M, size_t tid, const std::string &source, uint64_t currentTime){
std::stringstream rInfo; std::stringstream rInfo;
rInfo << "url=" << source << "/track" << trk.trackID << ";"; // get the current url, not localhost rInfo << "url=" << source << "/track" << tid << ";"; // get the current url, not localhost
rInfo << "sequence=" << pack.getSequence() << ";rtptime=" << currentTime * getMultiplier(trk); rInfo << "seq=" << pack.getSequence() << ";rtptime=" << currentTime * getMultiplier(&M, tid);
return rInfo.str(); return rInfo.str();
} }
@ -348,12 +337,11 @@ namespace SDP{
} }
void State::parseSDP(const std::string &sdp){ void State::parseSDP(const std::string &sdp){
DONTEVEN_MSG("Parsing %llu-byte SDP", sdp.size()); DONTEVEN_MSG("Parsing %zu-byte SDP", sdp.size());
std::stringstream ss(sdp); std::stringstream ss(sdp);
std::string to; std::string to;
uint64_t trackNo = 0; size_t tid = INVALID_TRACK_ID;
bool nope = true; // true if we have no valid track to fill bool nope = true; // true if we have no valid track to fill
DTSC::Track *thisTrack = 0;
while (std::getline(ss, to, '\n')){ while (std::getline(ss, to, '\n')){
if (!to.empty() && *to.rbegin() == '\r'){to.erase(to.size() - 1, 1);} if (!to.empty() && *to.rbegin() == '\r'){to.erase(to.size() - 1, 1);}
if (to.empty()){continue;} if (to.empty()){continue;}
@ -362,24 +350,23 @@ namespace SDP{
// All tracks start with a media line // All tracks start with a media line
if (to.substr(0, 2) == "m="){ if (to.substr(0, 2) == "m="){
nope = true; nope = true;
++trackNo; tid = myMeta->addTrack();
thisTrack = &(myMeta->tracks[trackNo]);
std::stringstream words(to.substr(2)); std::stringstream words(to.substr(2));
std::string item; std::string item;
if (getline(words, item, ' ') && (item == "audio" || item == "video")){ if (getline(words, item, ' ') && (item == "audio" || item == "video")){
thisTrack->type = item; myMeta->setType(tid, item);
thisTrack->trackID = trackNo; myMeta->setID(tid, tid);
}else{ }else{
WARN_MSG("Media type not supported: %s", item.c_str()); WARN_MSG("Media type not supported: %s", item.c_str());
myMeta->tracks.erase(trackNo); myMeta->removeTrack(tid);
tracks.erase(trackNo); tracks.erase(tid);
continue; continue;
} }
getline(words, item, ' '); getline(words, item, ' ');
if (!getline(words, item, ' ') || item.substr(0, 7) != "RTP/AVP"){ if (!getline(words, item, ' ') || item.substr(0, 7) != "RTP/AVP"){
WARN_MSG("Media transport not supported: %s", item.c_str()); WARN_MSG("Media transport not supported: %s", item.c_str());
myMeta->tracks.erase(trackNo); myMeta->removeTrack(tid);
tracks.erase(trackNo); tracks.erase(tid);
continue; continue;
} }
if (getline(words, item, ' ')){ if (getline(words, item, ' ')){
@ -388,62 +375,62 @@ namespace SDP{
case 0: // PCM Mu-law case 0: // PCM Mu-law
INFO_MSG("PCM Mu-law payload type"); INFO_MSG("PCM Mu-law payload type");
nope = false; nope = false;
thisTrack->codec = "ULAW"; myMeta->setCodec(tid, "ULAW");
thisTrack->rate = 8000; myMeta->setRate(tid, 8000);
thisTrack->channels = 1; myMeta->setChannels(tid, 1);
break; break;
case 8: // PCM A-law case 8: // PCM A-law
INFO_MSG("PCM A-law payload type"); INFO_MSG("PCM A-law payload type");
nope = false; nope = false;
thisTrack->codec = "ALAW"; myMeta->setCodec(tid, "ALAW");
thisTrack->rate = 8000; myMeta->setRate(tid, 8000);
thisTrack->channels = 1; myMeta->setChannels(tid, 1);
break; break;
case 10: // PCM Stereo, 44.1kHz case 10: // PCM Stereo, 44.1kHz
INFO_MSG("Linear PCM stereo 44.1kHz payload type"); INFO_MSG("Linear PCM stereo 44.1kHz payload type");
nope = false; nope = false;
thisTrack->codec = "PCM"; myMeta->setCodec(tid, "PCM");
thisTrack->size = 16; myMeta->setSize(tid, 16);
thisTrack->rate = 44100; myMeta->setRate(tid, 44100);
thisTrack->channels = 2; myMeta->setChannels(tid, 2);
break; break;
case 11: // PCM Mono, 44.1kHz case 11: // PCM Mono, 44.1kHz
INFO_MSG("Linear PCM mono 44.1kHz payload type"); INFO_MSG("Linear PCM mono 44.1kHz payload type");
nope = false; nope = false;
thisTrack->codec = "PCM"; myMeta->setCodec(tid, "PCM");
thisTrack->rate = 44100; myMeta->setRate(tid, 44100);
thisTrack->size = 16; myMeta->setSize(tid, 16);
thisTrack->channels = 1; myMeta->setChannels(tid, 1);
break; break;
case 14: // MPA case 14: // MPA
INFO_MSG("MPA payload type"); INFO_MSG("MPA payload type");
nope = false; nope = false;
thisTrack->codec = "MP3"; myMeta->setCodec(tid, "MP3");
thisTrack->rate = 0; myMeta->setRate(tid, 0);
thisTrack->size = 0; myMeta->setSize(tid, 0);
thisTrack->channels = 0; myMeta->setChannels(tid, 0);
break; break;
case 32: // MPV case 32: // MPV
INFO_MSG("MPV payload type"); INFO_MSG("MPV payload type");
nope = false; nope = false;
thisTrack->codec = "MPEG2"; myMeta->setCodec(tid, "MPEG2");
break; break;
default: default:
// dynamic type // dynamic type
if (avp_type >= 96 && avp_type <= 127){ if (avp_type >= 96 && avp_type <= 127){
HIGH_MSG("Dynamic payload type (%llu) detected", avp_type); HIGH_MSG("Dynamic payload type (%" PRIu64 ") detected", avp_type);
nope = false; nope = false;
continue; continue;
}else{ }else{
FAIL_MSG("Payload type %llu not supported!", avp_type); FAIL_MSG("Payload type %" PRIu64 " not supported!", avp_type);
myMeta->tracks.erase(trackNo); myMeta->removeTrack(tid);
tracks.erase(trackNo); tracks.erase(tid);
continue; continue;
} }
} }
} }
tConv[trackNo].setProperties(*thisTrack); tConv[tid].setProperties(*myMeta, tid);
HIGH_MSG("Incoming track %s", thisTrack->getIdentifier().c_str()); HIGH_MSG("Incoming track %s", myMeta->getTrackIdentifier(tid).c_str());
continue; continue;
} }
@ -456,62 +443,62 @@ namespace SDP{
for (unsigned int i = 0; i < trCodec.size(); ++i){ for (unsigned int i = 0; i < trCodec.size(); ++i){
if (trCodec[i] <= 122 && trCodec[i] >= 97){trCodec[i] -= 32;} if (trCodec[i] <= 122 && trCodec[i] >= 97){trCodec[i] -= 32;}
} }
if (thisTrack->type == "audio"){ if (myMeta->getType(tid) == "audio"){
std::string extraInfo = mediaType.substr(mediaType.find('/') + 1); std::string extraInfo = mediaType.substr(mediaType.find('/') + 1);
if (extraInfo.find('/') != std::string::npos){ if (extraInfo.find('/') != std::string::npos){
size_t lastSlash = extraInfo.find('/'); size_t lastSlash = extraInfo.find('/');
thisTrack->rate = atoll(extraInfo.substr(0, lastSlash).c_str()); myMeta->setRate(tid, atoll(extraInfo.substr(0, lastSlash).c_str()));
thisTrack->channels = atoll(extraInfo.substr(lastSlash + 1).c_str()); myMeta->setChannels(tid, atoll(extraInfo.substr(lastSlash + 1).c_str()));
}else{ }else{
thisTrack->rate = atoll(extraInfo.c_str()); myMeta->setRate(tid, atoll(extraInfo.c_str()));
thisTrack->channels = 1; myMeta->setChannels(tid, 1);
} }
} }
if (trCodec == "H264"){ if (trCodec == "H264"){
thisTrack->codec = "H264"; myMeta->setCodec(tid, "H264");
thisTrack->rate = 90000; myMeta->setRate(tid, 90000);
} }
if (trCodec == "H265"){ if (trCodec == "H265"){
thisTrack->codec = "HEVC"; myMeta->setCodec(tid, "HEVC");
thisTrack->rate = 90000; myMeta->setRate(tid, 90000);
} }
if (trCodec == "OPUS"){ if (trCodec == "OPUS"){
thisTrack->codec = "opus"; myMeta->setCodec(tid, "opus");
thisTrack->init = std::string("OpusHead\001\002\170\000\200\273\000\000\000\000\000", 19); myMeta->setInit(tid, "OpusHead\001\002\170\000\200\273\000\000\000\000\000", 19);
} }
if (trCodec == "PCMA"){thisTrack->codec = "ALAW";} if (trCodec == "PCMA"){myMeta->setCodec(tid, "ALAW");}
if (trCodec == "PCMU"){thisTrack->codec = "ULAW";} if (trCodec == "PCMU"){myMeta->setCodec(tid, "ULAW");}
if (trCodec == "L8"){ if (trCodec == "L8"){
thisTrack->codec = "PCM"; myMeta->setCodec(tid, "PCM");
thisTrack->size = 8; myMeta->setSize(tid, 8);
} }
if (trCodec == "L16"){ if (trCodec == "L16"){
thisTrack->codec = "PCM"; myMeta->setCodec(tid, "PCM");
thisTrack->size = 16; myMeta->setSize(tid, 16);
} }
if (trCodec == "L20"){ if (trCodec == "L20"){
thisTrack->codec = "PCM"; myMeta->setCodec(tid, "PCM");
thisTrack->size = 20; myMeta->setSize(tid, 20);
} }
if (trCodec == "L24" || trCodec == "PCM"){ if (trCodec == "L24" || trCodec == "PCM"){
thisTrack->codec = "PCM"; myMeta->setCodec(tid, "PCM");
thisTrack->size = 24; myMeta->setSize(tid, 24);
} }
if (trCodec == "MPEG4-GENERIC"){thisTrack->codec = "AAC";} if (trCodec == "MPEG4-GENERIC"){myMeta->setCodec(tid, "AAC");}
if (!thisTrack->codec.size()){ if (!myMeta->getCodec(tid).size()){
ERROR_MSG("Unsupported RTP mapping: %s", mediaType.c_str()); ERROR_MSG("Unsupported RTP mapping: %s", mediaType.c_str());
}else{ }else{
tConv[trackNo].setProperties(*thisTrack); tConv[tid].setProperties(*myMeta, tid);
HIGH_MSG("Incoming track %s", thisTrack->getIdentifier().c_str()); HIGH_MSG("Incoming track %s", myMeta->getTrackIdentifier(tid).c_str());
} }
continue; continue;
} }
if (to.substr(0, 10) == "a=control:"){ if (to.substr(0, 10) == "a=control:"){
tracks[trackNo].control = to.substr(10); tracks[tid].control = to.substr(10);
continue; continue;
} }
if (to.substr(0, 12) == "a=framerate:"){ if (to.substr(0, 12) == "a=framerate:"){
if (!thisTrack->rate){thisTrack->rate = atof(to.c_str() + 12) * 1000;} if (!myMeta->getRate(tid)){myMeta->setRate(tid, atof(to.c_str() + 12) * 1000);}
continue; continue;
} }
if (to.substr(0, 12) == "a=framesize:"){ if (to.substr(0, 12) == "a=framesize:"){
@ -525,75 +512,76 @@ namespace SDP{
continue; continue;
} }
if (to.substr(0, 7) == "a=fmtp:"){ if (to.substr(0, 7) == "a=fmtp:"){
tracks[trackNo].fmtp = to.substr(7); tracks[tid].fmtp = to.substr(7);
if (thisTrack->codec == "AAC"){ if (myMeta->getCodec(tid) == "AAC"){
if (tracks[trackNo].getParamString("mode") != "AAC-hbr"){ if (tracks[tid].getParamString("mode") != "AAC-hbr"){
// a=fmtp:97 // a=fmtp:97
// profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; // profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;
// config=120856E500 // config=120856E500
FAIL_MSG("AAC transport mode not supported: %s", tracks[trackNo].getParamString("mode").c_str()); FAIL_MSG("AAC transport mode not supported: %s", tracks[tid].getParamString("mode").c_str());
nope = true; nope = true;
myMeta->tracks.erase(trackNo); myMeta->removeTrack(tid);
tracks.erase(trackNo); tracks.erase(tid);
continue; continue;
} }
thisTrack->init = Encodings::Hex::decode(tracks[trackNo].getParamString("config")); myMeta->setInit(tid, Encodings::Hex::decode(tracks[tid].getParamString("config")));
// myMeta.tracks[trackNo].rate = aac::AudSpecConf::rate(myMeta.tracks[trackNo].init); // myMeta.tracks[trackNo].rate = aac::AudSpecConf::rate(myMeta.tracks[trackNo].init);
} }
if (thisTrack->codec == "H264"){ if (myMeta->getCodec(tid) == "H264"){
// a=fmtp:96 packetization-mode=1; // a=fmtp:96 packetization-mode=1;
// sprop-parameter-sets=Z0LAHtkA2D3m//AUABqxAAADAAEAAAMAMg8WLkg=,aMuDyyA=; // sprop-parameter-sets=Z0LAHtkA2D3m//AUABqxAAADAAEAAAMAMg8WLkg=,aMuDyyA=;
// profile-level-id=42C01E // profile-level-id=42C01E
std::string sprop = tracks[trackNo].getParamString("sprop-parameter-sets"); std::string sprop = tracks[tid].getParamString("sprop-parameter-sets");
size_t comma = sprop.find(','); size_t comma = sprop.find(',');
tracks[trackNo].spsData = Encodings::Base64::decode(sprop.substr(0, comma)); tracks[tid].spsData = Encodings::Base64::decode(sprop.substr(0, comma));
tracks[trackNo].ppsData = Encodings::Base64::decode(sprop.substr(comma + 1)); tracks[tid].ppsData = Encodings::Base64::decode(sprop.substr(comma + 1));
updateH264Init(trackNo); updateH264Init(tid);
} }
if (thisTrack->codec == "HEVC"){ if (myMeta->getCodec(tid) == "HEVC"){
tracks[trackNo].hevcInfo.addUnit(Encodings::Base64::decode(tracks[trackNo].getParamString("sprop-vps"))); tracks[tid].hevcInfo.addUnit(
tracks[trackNo].hevcInfo.addUnit(Encodings::Base64::decode(tracks[trackNo].getParamString("sprop-sps"))); Encodings::Base64::decode(tracks[tid].getParamString("sprop-vps")));
tracks[trackNo].hevcInfo.addUnit(Encodings::Base64::decode(tracks[trackNo].getParamString("sprop-pps"))); tracks[tid].hevcInfo.addUnit(
updateH265Init(trackNo); Encodings::Base64::decode(tracks[tid].getParamString("sprop-sps")));
tracks[tid].hevcInfo.addUnit(
Encodings::Base64::decode(tracks[tid].getParamString("sprop-pps")));
updateH265Init(tid);
} }
continue; continue;
} }
// We ignore bandwidth lines // We ignore bandwidth lines
if (to.substr(0, 2) == "b="){continue;} if (to.substr(0, 2) == "b="){continue;}
// we ignore everything before the first media line. // we ignore everything before the first media line.
if (!trackNo){continue;} if (tid == INVALID_TRACK_ID){continue;}
// at this point, the data is definitely for a track // at this point, the data is definitely for a track
INFO_MSG("Unhandled SDP line for track %llu: %s", trackNo, to.c_str()); INFO_MSG("Unhandled SDP line for track %zu: %s", tid, to.c_str());
} }
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta->tracks.begin(); std::set<size_t> validTracks = myMeta->getValidTracks();
it != myMeta->tracks.end(); ++it){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
INFO_MSG("Detected track %s", it->second.getIdentifier().c_str()); INFO_MSG("Detected track %s", myMeta->getTrackIdentifier(*it).c_str());
} }
} }
/// Calculates H265 track metadata from sps and pps data stored in tracks[trackNo] /// Calculates H265 track metadata from sps and pps data stored in tracks[trackNo]
void State::updateH265Init(uint64_t trackNo){ void State::updateH265Init(size_t tid){
DTSC::Track &Trk = myMeta->tracks[trackNo]; SDP::Track &RTrk = tracks[tid];
SDP::Track &RTrk = tracks[trackNo];
if (!RTrk.hevcInfo.haveRequired()){ if (!RTrk.hevcInfo.haveRequired()){
MEDIUM_MSG("Aborted meta fill for hevc track %lu: no info nal unit", trackNo); MEDIUM_MSG("Aborted meta fill for hevc track %lu: no info nal unit", tid);
return; return;
} }
Trk.init = RTrk.hevcInfo.generateHVCC(); myMeta->setInit(tid, RTrk.hevcInfo.generateHVCC());
h265::metaInfo MI = tracks[trackNo].hevcInfo.getMeta(); h265::metaInfo MI = tracks[tid].hevcInfo.getMeta();
RTrk.fpsMeta = MI.fps; RTrk.fpsMeta = MI.fps;
Trk.width = MI.width; myMeta->setWidth(tid, MI.width);
Trk.height = MI.height; myMeta->setHeight(tid, MI.height);
Trk.fpks = RTrk.fpsMeta * 1000; myMeta->setFpks(tid, RTrk.fpsMeta * 1000);
tConv[trackNo].setProperties(Trk); tConv[tid].setProperties(*myMeta, tid);
} }
/// Calculates H264 track metadata from vps, sps and pps data stored in tracks[trackNo] /// Calculates H264 track metadata from vps, sps and pps data stored in tracks[trackNo]
void State::updateH264Init(uint64_t trackNo){ void State::updateH264Init(uint64_t tid){
DTSC::Track &Trk = myMeta->tracks[trackNo]; SDP::Track &RTrk = tracks[tid];
SDP::Track &RTrk = tracks[trackNo];
h264::sequenceParameterSet sps(RTrk.spsData.data(), RTrk.spsData.size()); h264::sequenceParameterSet sps(RTrk.spsData.data(), RTrk.spsData.size());
h264::SPSMeta hMeta = sps.getCharacteristics(); h264::SPSMeta hMeta = sps.getCharacteristics();
MP4::AVCC avccBox; MP4::AVCC avccBox;
@ -606,27 +594,27 @@ namespace SDP{
avccBox.setPPSCount(1); avccBox.setPPSCount(1);
avccBox.setPPS(RTrk.ppsData); avccBox.setPPS(RTrk.ppsData);
RTrk.fpsMeta = hMeta.fps; RTrk.fpsMeta = hMeta.fps;
Trk.width = hMeta.width; myMeta->setWidth(tid, hMeta.width);
Trk.height = hMeta.height; myMeta->setHeight(tid, hMeta.height);
Trk.fpks = hMeta.fps * 1000; myMeta->setFpks(tid, hMeta.fps * 1000);
Trk.init = std::string(avccBox.payload(), avccBox.payloadSize()); myMeta->setInit(tid, avccBox.payload(), avccBox.payloadSize());
tConv[trackNo].setProperties(Trk); tConv[tid].setProperties(*myMeta, tid);
} }
uint32_t State::getTrackNoForChannel(uint8_t chan){ size_t State::getTrackNoForChannel(uint8_t chan){
for (std::map<uint32_t, Track>::iterator it = tracks.begin(); it != tracks.end(); ++it){ for (std::map<size_t, Track>::iterator it = tracks.begin(); it != tracks.end(); ++it){
if (chan == it->second.channel){return it->first;} if (chan == it->second.channel){return it->first;}
} }
return 0; return INVALID_TRACK_ID;
} }
uint32_t State::parseSetup(HTTP::Parser &H, const std::string &cH, const std::string &src){ size_t State::parseSetup(HTTP::Parser &H, const std::string &cH, const std::string &src){
static uint32_t trackCounter = 0; static uint32_t trackCounter = 0;
if (H.url == "200"){ if (H.url == "200"){
++trackCounter; ++trackCounter;
if (!tracks.count(trackCounter)){return 0;} if (!tracks.count(trackCounter)){return INVALID_TRACK_ID;}
if (!tracks[trackCounter].parseTransport(H.GetHeader("Transport"), cH, src, myMeta->tracks[trackCounter])){ if (!tracks[trackCounter].parseTransport(H.GetHeader("Transport"), cH, src, myMeta, trackCounter)){
return 0; return INVALID_TRACK_ID;
} }
return trackCounter; return trackCounter;
} }
@ -638,7 +626,7 @@ namespace SDP{
while (loop){ while (loop){
if (tracks.size()){ if (tracks.size()){
for (std::map<uint32_t, Track>::iterator it = tracks.begin(); it != tracks.end(); ++it){ for (std::map<size_t, Track>::iterator it = tracks.begin(); it != tracks.end(); ++it){
if (!it->second.control.size()){ if (!it->second.control.size()){
it->second.control = "/track" + JSON::Value(it->first).asString(); it->second.control = "/track" + JSON::Value(it->first).asString();
INFO_MSG("Control track: %s", it->second.control.c_str()); INFO_MSG("Control track: %s", it->second.control.c_str());
@ -649,8 +637,8 @@ namespace SDP{
(pw.size() >= it->second.control.size() && (pw.size() >= it->second.control.size() &&
pw.substr(pw.size() - it->second.control.size()) == it->second.control)){ pw.substr(pw.size() - it->second.control.size()) == it->second.control)){
INFO_MSG("Parsing SETUP against track %lu", it->first); INFO_MSG("Parsing SETUP against track %lu", it->first);
if (!it->second.parseTransport(H.GetHeader("Transport"), cH, src, myMeta->tracks[it->first])){ if (!it->second.parseTransport(H.GetHeader("Transport"), cH, src, myMeta, it->first)){
return 0; return INVALID_TRACK_ID;
} }
return it->first; return it->first;
} }
@ -658,13 +646,13 @@ namespace SDP{
} }
if (H.url.find("/track") != std::string::npos){ if (H.url.find("/track") != std::string::npos){
uint32_t trackNo = atoi(H.url.c_str() + H.url.find("/track") + 6); uint32_t trackNo = atoi(H.url.c_str() + H.url.find("/track") + 6);
if (trackNo){ // if (trackNo){
INFO_MSG("Parsing SETUP against track %lu", trackNo); INFO_MSG("Parsing SETUP against track %" PRIu32, trackNo);
if (!tracks[trackNo].parseTransport(H.GetHeader("Transport"), cH, src, myMeta->tracks[trackNo])){ if (!tracks[trackNo].parseTransport(H.GetHeader("Transport"), cH, src, myMeta, trackNo)){
return 0; return INVALID_TRACK_ID;
}
return trackNo;
} }
return trackNo;
//}
} }
if (urlString != url.path){ if (urlString != url.path){
urlString = url.path; urlString = url.path;
@ -672,18 +660,20 @@ namespace SDP{
loop = false; loop = false;
} }
} }
return 0; return INVALID_TRACK_ID;
} }
/// Returns the multiplier to use to get milliseconds from the RTP payload type for the given /// Returns the multiplier to use to get milliseconds from the RTP payload type for the given
/// track /// track
double getMultiplier(const DTSC::Track &Trk){ double getMultiplier(const DTSC::Meta *M, size_t tid){
if (Trk.type == "video" || Trk.codec == "MP2" || Trk.codec == "MP3"){return 90.0;} if (M->getType(tid) == "video" || M->getCodec(tid) == "MP2" || M->getCodec(tid) == "MP3"){
return ((double)Trk.rate / 1000.0); return 90.0;
}
return ((double)M->getRate(tid) / 1000.0);
} }
void State::updateInit(const uint64_t trackNo, const std::string &initData){ void State::updateInit(const size_t tid, const std::string &initData){
if (myMeta->tracks.count(trackNo)){myMeta->tracks[trackNo].init = initData;} myMeta->setInit(tid, initData.data(), initData.size());
} }
/// Handles RTP packets generically, for both TCP and UDP-based connections. /// Handles RTP packets generically, for both TCP and UDP-based connections.

View file

@ -7,7 +7,7 @@
namespace SDP{ namespace SDP{
double getMultiplier(const DTSC::Track &Trk); double getMultiplier(const DTSC::Meta *M, size_t tid);
/// Structure used to keep track of selected tracks. /// Structure used to keep track of selected tracks.
class Track{ class Track{
@ -17,8 +17,8 @@ namespace SDP{
std::string getParamString(const std::string &param) const; std::string getParamString(const std::string &param) const;
uint64_t getParamInt(const std::string &param) const; uint64_t getParamInt(const std::string &param) const;
bool parseTransport(const std::string &transport, const std::string &host, bool parseTransport(const std::string &transport, const std::string &host,
const std::string &source, const DTSC::Track &trk); const std::string &source, const DTSC::Meta *M, size_t tid);
std::string rtpInfo(const DTSC::Track &trk, const std::string &source, uint64_t currentTime); std::string rtpInfo(const DTSC::Meta &M, size_t tid, const std::string &source, uint64_t currentTime);
public: public:
Socket::UDPConnection data; Socket::UDPConnection data;
@ -47,18 +47,18 @@ namespace SDP{
void (*incomingPacketCallback)(const DTSC::Packet &pkt); void (*incomingPacketCallback)(const DTSC::Packet &pkt);
void parseSDP(const std::string &sdp); void parseSDP(const std::string &sdp);
void parseSDPEx(const std::string &sdp); void parseSDPEx(const std::string &sdp);
void updateH264Init(uint64_t trackNo); void updateH264Init(size_t trackNo);
void updateH265Init(uint64_t trackNo); void updateH265Init(size_t tid);
void updateInit(const uint64_t trackNo, const std::string &initData); void updateInit(const uint64_t trackNo, const std::string &initData);
uint32_t getTrackNoForChannel(uint8_t chan); size_t getTrackNoForChannel(uint8_t chan);
uint32_t parseSetup(HTTP::Parser &H, const std::string &host, const std::string &source); size_t parseSetup(HTTP::Parser &H, const std::string &host, const std::string &source);
void handleIncomingRTP(const uint64_t track, const RTP::Packet &pkt); void handleIncomingRTP(const uint64_t track, const RTP::Packet &pkt);
public: public:
DTSC::Meta *myMeta; DTSC::Meta *myMeta;
std::map<uint32_t, RTP::toDTSC> tConv; ///< Converters to DTSC std::map<size_t, RTP::toDTSC> tConv; ///< Converters to DTSC
std::map<uint32_t, Track> tracks; ///< List of selected tracks with SDP-specific session data. std::map<size_t, Track> tracks; ///< List of selected tracks with SDP-specific session data.
}; };
std::string mediaDescription(const DTSC::Track &trk); std::string mediaDescription(const DTSC::Meta *M, size_t tid);
}// namespace SDP }// namespace SDP

View file

@ -488,7 +488,7 @@ namespace SDP{
MediaFormat *Media::getFormatForPayloadType(uint64_t &payloadType){ MediaFormat *Media::getFormatForPayloadType(uint64_t &payloadType){
std::map<uint64_t, MediaFormat>::iterator it = formats.find(payloadType); std::map<uint64_t, MediaFormat>::iterator it = formats.find(payloadType);
if (it == formats.end()){ if (it == formats.end()){
ERROR_MSG("No format found for payload type: %u.", payloadType); ERROR_MSG("No format found for payload type: %" PRIu64 ".", payloadType);
return NULL; return NULL;
} }
return &it->second; return &it->second;
@ -581,6 +581,14 @@ namespace SDP{
return false; return false;
} }
bool Session::hasSendOnlyMedia(){
size_t numMedias = medias.size();
for (size_t i = 0; i < numMedias; ++i){
if (medias[i].direction == "sendonly"){return true;}
}
return false;
}
bool Session::parseSDP(const std::string &sdp){ bool Session::parseSDP(const std::string &sdp){
if (sdp.empty()){ if (sdp.empty()){
@ -760,7 +768,7 @@ namespace SDP{
} }
Answer::Answer() Answer::Answer()
: isVideoEnabled(false), isAudioEnabled(false), candidatePort(0), : isAudioEnabled(false), isVideoEnabled(false), candidatePort(0),
videoLossPrevention(SDP_LOSS_PREVENTION_NONE){} videoLossPrevention(SDP_LOSS_PREVENTION_NONE){}
bool Answer::parseOffer(const std::string &sdp){ bool Answer::parseOffer(const std::string &sdp){
@ -820,45 +828,44 @@ namespace SDP{
direction = dir; direction = dir;
} }
bool Answer::setupVideoDTSCTrack(DTSC::Track &result){ bool Answer::setupVideoDTSCTrack(DTSC::Meta &M, size_t tid){
if (!isVideoEnabled){ if (!isVideoEnabled){
FAIL_MSG("Video is disabled; cannot setup DTSC::Track."); FAIL_MSG("Video is disabled; cannot setup DTSC::Track.");
return false; return false;
} }
result.codec = codecRTP2Mist(answerVideoFormat.encodingName); M.setCodec(tid, codecRTP2Mist(answerVideoFormat.encodingName));
if (result.codec.empty()){ if (M.getCodec(tid).empty()){
FAIL_MSG("Failed to convert the format codec into one that MistServer understands. %s.", FAIL_MSG("Failed to convert the format codec into one that MistServer understands. %s.",
answerVideoFormat.encodingName.c_str()); answerVideoFormat.encodingName.c_str());
return false; return false;
} }
M.setType(tid, "video");
result.type = "video"; M.setRate(tid, answerVideoFormat.getVideoRate());
result.rate = answerVideoFormat.getVideoRate(); M.setID(tid, answerVideoFormat.payloadType);
result.trackID = answerVideoFormat.payloadType; INFO_MSG("Setup video track %zu for payload type %zu", tid, answerVideoFormat.payloadType);
return true; return true;
} }
bool Answer::setupAudioDTSCTrack(DTSC::Track &result){ bool Answer::setupAudioDTSCTrack(DTSC::Meta &M, size_t tid){
if (!isAudioEnabled){ if (!isAudioEnabled){
FAIL_MSG("Audio is disabled; cannot setup DTSC::Track."); FAIL_MSG("Audio is disabled; cannot setup DTSC::Track.");
return false; return false;
} }
result.codec = codecRTP2Mist(answerAudioFormat.encodingName); M.setCodec(tid, codecRTP2Mist(answerAudioFormat.encodingName));
if (result.codec.empty()){ if (M.getCodec(tid).empty()){
FAIL_MSG("Failed to convert the format codec into one that MistServer understands. %s.", FAIL_MSG("Failed to convert the format codec into one that MistServer understands. %s.",
answerAudioFormat.encodingName.c_str()); answerAudioFormat.encodingName.c_str());
return false; return false;
} }
result.type = "audio"; M.setType(tid, "audio");
result.rate = answerAudioFormat.getAudioSampleRate(); M.setRate(tid, answerAudioFormat.getAudioSampleRate());
result.channels = answerAudioFormat.getAudioNumChannels(); M.setChannels(tid, answerAudioFormat.getAudioNumChannels());
result.size = answerAudioFormat.getAudioBitSize(); M.setSize(tid, answerAudioFormat.getAudioBitSize());
result.trackID = answerAudioFormat.payloadType; M.setID(tid, answerAudioFormat.payloadType);
INFO_MSG("Setup audio track %zu for payload time %zu", tid, answerAudioFormat.payloadType);
return true; return true;
} }
@ -1023,7 +1030,9 @@ namespace SDP{
return result; return result;
} }
void Answer::addLine(const std::string &fmt, ...){ // The parameter here is NOT a reference, because va_start specifies that its parameter is not
// allowed to be one.
void Answer::addLine(const std::string fmt, ...){
char buffer[1024] ={}; char buffer[1024] ={};
va_list args; va_list args;

View file

@ -50,7 +50,8 @@ namespace SDP{
uint64_t getPayloadType() const; ///< Returns the `payloadType` member. uint64_t getPayloadType() const; ///< Returns the `payloadType` member.
int32_t getPacketizationModeForH264(); ///< When this represents a h264 format this will return the int32_t getPacketizationModeForH264(); ///< When this represents a h264 format this will return the
///< packetization mode when it was provided in the SDP ///< packetization mode when it was provided in the SDP
std::string getProfileLevelIdForH264(); ///< When this represents a H264 format, this will return the profile-level-id from the format parameters. std::string getProfileLevelIdForH264(); ///< When this represents a H264 format, this will return the
///< profile-level-id from the format parameters.
operator bool() const; operator bool() const;
@ -144,6 +145,9 @@ namespace SDP{
bool hasReceiveOnlyMedia(); ///< Returns true when one of the media sections has a `a=recvonly` bool hasReceiveOnlyMedia(); ///< Returns true when one of the media sections has a `a=recvonly`
///< attribute. This is used to determine if the other peer only ///< attribute. This is used to determine if the other peer only
///< wants to receive or also sent data. */ ///< wants to receive or also sent data. */
bool hasSendOnlyMedia(); ///< Returns true when one of the media sections has a `a=sendonly`
///< attribute. This is used to determine if the other peer only
///< wants to receive or also sent data. */
public: public:
std::vector<SDP::Media> medias; ///< For each `m=` line we create a `SDP::Media` instance. The std::vector<SDP::Media> medias; ///< For each `m=` line we create a `SDP::Media` instance. The
@ -166,14 +170,14 @@ namespace SDP{
void setFingerprint(const std::string &fingerprintSha); ///< Set the SHA265 that represents the void setFingerprint(const std::string &fingerprintSha); ///< Set the SHA265 that represents the
///< certificate that is used with DTLS. ///< certificate that is used with DTLS.
void setDirection(const std::string &dir); void setDirection(const std::string &dir);
bool setupVideoDTSCTrack(DTSC::Track &result); bool setupVideoDTSCTrack(DTSC::Meta &M, size_t tid);
bool setupAudioDTSCTrack(DTSC::Track &result); bool setupAudioDTSCTrack(DTSC::Meta &M, size_t tid);
std::string toString(); std::string toString();
private: private:
bool enableMedia(const std::string &type, const std::string &codecName, SDP::Media &outMedia, bool enableMedia(const std::string &type, const std::string &codecName, SDP::Media &outMedia,
SDP::MediaFormat &outFormat); SDP::MediaFormat &outFormat);
void addLine(const std::string &fmt, ...); void addLine(const std::string fmt, ...);
std::string generateSessionId(); std::string generateSessionId();
std::string generateIceUFrag(); ///< Generates the `ice-ufrag` value. std::string generateIceUFrag(); ///< Generates the `ice-ufrag` value.
std::string generateIcePwd(); ///< Generates the `ice-pwd` value. std::string generateIcePwd(); ///< Generates the `ice-pwd` value.

View file

@ -22,11 +22,6 @@
#include <windows.h> #include <windows.h>
#endif #endif
/// Forces a disconnect to all users.
static void killStatistics(char *data, size_t len, unsigned int id){
(*(data - 1)) = 60 | ((*(data - 1)) & 0x80); // Send disconnect message;
}
namespace IPC{ namespace IPC{
#if defined(__CYGWIN__) || defined(_WIN32) #if defined(__CYGWIN__) || defined(_WIN32)
@ -48,8 +43,9 @@ namespace IPC{
///\brief Constructs a named semaphore ///\brief Constructs a named semaphore
///\param name The name of the semaphore ///\param name The name of the semaphore
///\param oflag The flags with which to open the semaphore ///\param oflag The flags with which to open the semaphore
///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored otherwise ///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored
///\param value The initial value of the semaphore if O_CREAT is given in oflag, ignored otherwise /// otherwise \param value The initial value of the semaphore if O_CREAT is given in oflag,
/// ignored otherwise
semaphore::semaphore(const char *name, int oflag, mode_t mode, unsigned int value, bool noWait){ semaphore::semaphore(const char *name, int oflag, mode_t mode, unsigned int value, bool noWait){
#if defined(__CYGWIN__) || defined(_WIN32) #if defined(__CYGWIN__) || defined(_WIN32)
mySem = 0; mySem = 0;
@ -77,8 +73,9 @@ namespace IPC{
/// Closes currently opened semaphore if needed /// Closes currently opened semaphore if needed
///\param name The name of the semaphore ///\param name The name of the semaphore
///\param oflag The flags with which to open the semaphore ///\param oflag The flags with which to open the semaphore
///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored otherwise ///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored
///\param value The initial value of the semaphore if O_CREAT is given in oflag, ignored otherwise /// otherwise \param value The initial value of the semaphore if O_CREAT is given in oflag,
/// ignored otherwise
void semaphore::open(const char *name, int oflag, mode_t mode, unsigned int value, bool noWait){ void semaphore::open(const char *name, int oflag, mode_t mode, unsigned int value, bool noWait){
close(); close();
int timer = 0; int timer = 0;
@ -139,7 +136,8 @@ namespace IPC{
int semaphore::getVal() const{ int semaphore::getVal() const{
#if defined(__CYGWIN__) || defined(_WIN32) #if defined(__CYGWIN__) || defined(_WIN32)
LONG res; LONG res;
ReleaseSemaphore(mySem, 0, &res); // not really release.... just checking to see if I can get the value this way ReleaseSemaphore(mySem, 0,
&res); // not really release.... just checking to see if I can get the value this way
#else #else
int res; int res;
sem_getvalue(mySem, &res); sem_getvalue(mySem, &res);
@ -202,7 +200,41 @@ namespace IPC{
return isLocked; return isLocked;
} }
///\brief Tries to wait for the semaphore for a single second, returns true if successful, false otherwise ///\brief Tries to wait for the semaphore for a given amount of ms, returns true if successful, false
/// otherwise
bool semaphore::tryWait(uint64_t ms){
if (!(*this)){return false;}
int result;
#if defined(__CYGWIN__) || defined(_WIN32)
result = WaitForSingleObject(mySem, ms); // wait at most 1s
if (result == 0x80){
WARN_MSG("Consistency error caught on semaphore %s", myName.c_str());
result = 0;
}
#elif defined(__APPLE__)
/// \todo (roxlu) test tryWaitOneSecond, shared_memory.cpp
uint64_t now = Util::getMicros();
uint64_t timeout = now + (ms * 1000);
while (now < timeout){
if (0 == sem_trywait(mySem)){
isLocked = true;
return true;
}
usleep(100e3);
now = Util::getMicros();
}
return false;
#else
struct timespec wt;
wt.tv_sec = ms / 1000;
wt.tv_nsec = ms % 1000;
result = sem_timedwait(mySem, &wt);
#endif
return isLocked = (result == 0);
}
///\brief Tries to wait for the semaphore for a single second, returns true if successful, false
/// otherwise
bool semaphore::tryWaitOneSecond(){ bool semaphore::tryWaitOneSecond(){
if (!(*this)){return false;} if (!(*this)){return false;}
int result; int result;
@ -598,581 +630,9 @@ namespace IPC{
///\brief Default destructor ///\brief Default destructor
sharedFile::~sharedFile(){close();} sharedFile::~sharedFile(){close();}
///\brief StatExchange constructor, sets the datapointer to the given value
statExchange::statExchange(char *_data) : data(_data){}
///\brief Sets timestamp of the current stats
void statExchange::now(long long int time){Bit::htobll(data, time);}
///\brief Gets timestamp of the current stats
long long int statExchange::now(){
long long int result;
return Bit::btohll(data);
}
///\brief Sets time currently connected
void statExchange::time(long time){Bit::htobl(data + 8, time);}
/// Calculates session ID from CRC, stream name, connector and host.
std::string statExchange::getSessId(){return Secure::md5(data + 32, 140);}
///\brief Gets time currently connected
long statExchange::time(){return Bit::btohl(data + 8);}
///\brief Sets the last viewing second of this user
void statExchange::lastSecond(long time){Bit::htobl(data + 12, time);}
///\brief Gets the last viewing second of this user
long statExchange::lastSecond(){return Bit::btohl(data + 12);}
///\brief Sets the amount of bytes received
void statExchange::down(long long int bytes){Bit::htobll(data + 16, bytes);}
///\brief Gets the amount of bytes received
long long int statExchange::down(){return Bit::btohll(data + 16);}
///\brief Sets the amount of bytes sent
void statExchange::up(long long int bytes){Bit::htobll(data + 24, bytes);}
///\brief Gets the amount of bytes sent
long long int statExchange::up(){return Bit::btohll(data + 24);}
///\brief Sets the host of this connection
void statExchange::host(std::string name){
if (name.size() < 16){memset(data + 32, 0, 16);}
memcpy(data + 32, name.c_str(), std::min((int)name.size(), 16));
}
///\brief Gets the host of this connection
std::string statExchange::host(){return std::string(data + 32, 16);}
///\brief Sets the name of the stream this user is viewing
void statExchange::streamName(std::string name){
size_t splitChar = name.find_first_of("+ ");
if (splitChar != std::string::npos){name[splitChar] = '+';}
snprintf(data + 48, 100, "%s", name.c_str());
}
///\brief Gets the name of the stream this user is viewing
std::string statExchange::streamName(){return std::string(data + 48, strnlen(data + 48, 100));}
///\brief Sets the name of the connector through which this user is viewing
void statExchange::connector(std::string name){snprintf(data + 148, 20, "%s", name.c_str());}
///\brief Gets the name of the connector through which this user is viewing
std::string statExchange::connector(){
return std::string(data + 148, std::min((int)strlen(data + 148), 20));
}
///\brief Sets checksum field
void statExchange::crc(unsigned int sum){Bit::htobl(data + 168, sum);}
///\brief Gets checksum field
unsigned int statExchange::crc(){return Bit::btohl(data + 168);}
///\brief Sets checksum field
void statExchange::setSync(char s){data[172] = s;}
///\brief Gets checksum field
char statExchange::getSync(){return data[172];}
///\brief Gets PID field
uint32_t statExchange::getPID(){return *(uint32_t *)(data + 173);}
///\brief Creates a semaphore guard, locks the semaphore on call ///\brief Creates a semaphore guard, locks the semaphore on call
semGuard::semGuard(semaphore *thisSemaphore) : mySemaphore(thisSemaphore){mySemaphore->wait();} semGuard::semGuard(semaphore *thisSemaphore) : mySemaphore(thisSemaphore){mySemaphore->wait();}
///\brief Destructs a semaphore guard, unlocks the semaphore on call ///\brief Destructs a semaphore guard, unlocks the semaphore on call
semGuard::~semGuard(){mySemaphore->post();} semGuard::~semGuard(){mySemaphore->post();}
///\brief Default constructor, erases all the values
sharedServer::sharedServer(){
payLen = 0;
hasCounter = false;
amount = 0;
}
///\brief Desired constructor, initializes after cleaning.
///\param name The basename of this server
///\param len The lenght of the payload
///\param withCounter Whether the content should have a counter
sharedServer::sharedServer(std::string name, int len, bool withCounter){
sharedServer();
init(name, len, withCounter);
}
///\brief Initialize the server
///\param name The basename of this server
///\param len The lenght of the payload
///\param withCounter Whether the content should have a counter
void sharedServer::init(std::string name, int len, bool withCounter){
if (mySemaphore){mySemaphore.close();}
if (baseName != ""){mySemaphore.unlink();}
myPages.clear();
baseName = "/" + name;
payLen = len;
hasCounter = withCounter;
mySemaphore.open(baseName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
if (!mySemaphore){
DEBUG_MSG(DLVL_FAIL, "Creating semaphore failed: %s", strerror(errno));
return;
}else{
semGuard tmpGuard(&mySemaphore);
amount = 0;
newPage();
}
}
///\brief The deconstructor
sharedServer::~sharedServer(){mySemaphore.unlink();}
///\brief Determines whether a sharedServer is valid
sharedServer::operator bool() const{return myPages.size();}
/// Sets all currently loaded memory pages to non-master, so they are not cleaned up on
/// destruction, but left behind. Useful for doing rolling updates and such.
void sharedServer::abandon(){
if (!myPages.size()){return;}
VERYHIGH_MSG("Abandoning %llu memory pages, leaving them behind on purpose", myPages.size());
for (std::deque<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++){
(*it).master = false;
}
}
///\brief Creates the next page with the correct size
void sharedServer::newPage(){
sharedPage tmp(std::string(baseName.substr(1) + (char)(myPages.size() + (int)'A')),
std::min(((8192 * 2) << myPages.size()), (32 * 1024 * 1024)), false, false);
if (!tmp.mapped){
tmp.init(std::string(baseName.substr(1) + (char)(myPages.size() + (int)'A')),
std::min(((8192 * 2) << myPages.size()), (32 * 1024 * 1024)), true);
tmp.master = false;
}
myPages.push_back(tmp);
myPages.back().master = true;
VERYHIGH_MSG("Created a new page: %s", tmp.name.c_str());
amount += (32 * 1024 * 1024) * myPages.size(); // assume maximum load - we don't want to miss any entries
}
///\brief Deletes the highest allocated page
void sharedServer::deletePage(){
if (myPages.size() == 1){
DEBUG_MSG(DLVL_WARN, "Can't remove last page for %s", baseName.c_str());
return;
}
myPages.pop_back();
}
///\brief Determines whether an id is currently in use or not
bool sharedServer::isInUse(unsigned int id){
unsigned int i = 0;
for (std::deque<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++){
// return if we reached the end
if (!it->mapped || !it->len){return false;}
// not on this page? skip to next.
if (it->len < (id - i) * payLen){
i += it->len / payLen;
continue;
}
if (hasCounter){
// counter? return true if it is non-zero.
return (it->mapped[(id - i) * payLen] != 0);
}else{
// no counter - check the entire size for being all zeroes.
for (unsigned int j = 0; j < payLen; ++j){
if (it->mapped[(id - i) * payLen + j]){return true;}
}
return false;
}
}
// only happens if we run out of pages
return false;
}
/// Disconnect all connected users, waits at most 2.5 seconds until completed
void sharedServer::finishEach(){
if (!hasCounter){return;}
unsigned int c = 0; // to prevent eternal loops
do{
parseEach(killStatistics);
Util::wait(250);
}while (amount > 1 && c++ < 10);
}
/// Returns a pointer to the data for the given index.
/// Returns null on error or if index is empty.
char *sharedServer::getIndex(unsigned int requestId){
char *empty = 0;
if (!hasCounter){
empty = (char *)malloc(payLen * sizeof(char));
memset(empty, 0, payLen);
}
unsigned int id = 0;
for (std::deque<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++){
if (!it->mapped || !it->len){
DEBUG_MSG(DLVL_FAIL, "Something went terribly wrong?");
return 0;
}
unsigned int offset = 0;
while (offset + payLen + (hasCounter ? 1 : 0) <= it->len){
if (id == requestId){
if (hasCounter){
if (it->mapped[offset] != 0){
return it->mapped + offset + 1;
}else{
return 0;
}
}else{
if (memcmp(empty, it->mapped + offset, payLen)){
return it->mapped + offset;
}else{
return 0;
}
}
}
offset += payLen + (hasCounter ? 1 : 0);
id++;
}
}
return 0;
}
///\brief Parse each of the possible payload pieces, and runs a callback on it if in use.
void sharedServer::parseEach(void (*activeCallback)(char *data, size_t len, unsigned int id),
void (*disconCallback)(char *data, size_t len, unsigned int id)){
char *empty = 0;
if (!hasCounter){
empty = (char *)malloc(payLen * sizeof(char));
memset(empty, 0, payLen);
}
unsigned int id = 0;
unsigned int userCount = 0;
unsigned int emptyCount = 0;
unsigned int lastFilled = 0;
connectedUsers = 0;
for (std::deque<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++){
if (!it->mapped || !it->len){
DEBUG_MSG(DLVL_FAIL, "Something went terribly wrong?");
break;
}
userCount = 0;
unsigned int offset = 0;
while (offset + payLen + (hasCounter ? 1 : 0) <= it->len){
if (hasCounter){
if (it->mapped[offset] != 0){
char *counter = it->mapped + offset;
// increase the count if needed
++userCount;
if (*counter & 0x80){connectedUsers++;}
char countNum = (*counter) & 0x7F;
lastFilled = id;
if (id >= amount){
amount = id + 1;
VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount);
}
uint32_t tmpPID = *((uint32_t *)(it->mapped + 1 + offset + payLen - 4));
if (tmpPID > 1 && it->master && !Util::Procs::isRunning(tmpPID) &&
!(countNum == 126 || countNum == 127)){
WARN_MSG("process disappeared, timing out. (pid %lu)", tmpPID);
*counter = 125 | (0x80 & (*counter)); // if process is already dead, instant timeout.
}
activeCallback(it->mapped + offset + 1, payLen, id);
switch (countNum){
case 127: HIGH_MSG("Client %u requested disconnect", id); break;
case 126: HIGH_MSG("Client %u timed out", id); break;
default:
#ifndef NOCRASHCHECK
if (tmpPID > 1 && it->master){
if (countNum > 10 && countNum < 60){
if (countNum < 30){
if (countNum > 15){WARN_MSG("Process %d is unresponsive", tmpPID);}
Util::Procs::Stop(tmpPID); // soft kill
}else{
ERROR_MSG("Killing unresponsive process %d", tmpPID);
Util::Procs::Murder(tmpPID); // improved kill
}
}
if (countNum > 70){
if (countNum < 90){
if (countNum > 75){WARN_MSG("Stopping process %d is unresponsive", tmpPID);}
Util::Procs::Stop(tmpPID); // soft kill
}else{
ERROR_MSG("Killing unresponsive stopping process %d", tmpPID);
Util::Procs::Murder(tmpPID); // improved kill
}
}
}
#endif
break;
}
if (countNum == 127 || countNum == 126){
semGuard tmpGuard(&mySemaphore);
if (disconCallback){disconCallback(counter + 1, payLen, id);}
memset(counter + 1, 0, payLen);
*counter = 0;
}else{
++(*counter);
}
}else{
// stop if we're past the amount counted and we're empty
if (id >= amount){
// bring the counter down if this was the last element
if (lastFilled + 1 < amount){
amount = lastFilled + 1;
VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount);
}
if (id >= amount + 100){
// stop, we're guaranteed no more pages are full at this point
break;
}
}
}
}else{
if (memcmp(empty, it->mapped + offset, payLen)){
++userCount;
// increase the count if needed
lastFilled = id;
if (id >= amount){
amount = id + 1;
VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount);
}
activeCallback(it->mapped + offset, payLen, id);
}else{
// stop if we're past the amount counted and we're empty
if (id >= amount){
// bring the counter down if this was the last element
if (lastFilled + 1 < amount){
amount = lastFilled + 1;
VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount);
}
if (id >= amount + 100){
// stop, we're guaranteed no more pages are full at this point
if (empty){free(empty);}
break;
}
}
}
}
offset += payLen + (hasCounter ? 1 : 0);
id++;
}
if (userCount == 0){
++emptyCount;
}else{
emptyCount = 0;
std::deque<sharedPage>::iterator tIt = it;
if (++tIt == myPages.end()){
bool unsetMaster = !(it->master);
semGuard tmpGuard(&mySemaphore);
newPage();
if (unsetMaster){(myPages.end() - 1)->master = false;}
it = myPages.end() - 2;
}
}
}
if (emptyCount > 1){
semGuard tmpGuard(&mySemaphore);
deletePage();
}
if (empty){free(empty);}
}
///\brief Creates an empty shared client
sharedClient::sharedClient(){
hasCounter = 0;
payLen = 0;
offsetOnPage = 0;
countAsViewer = true;
}
///\brief Copy constructor for sharedClients
///\param rhs The client ro copy
sharedClient::sharedClient(const sharedClient &rhs){
countAsViewer = rhs.countAsViewer;
baseName = rhs.baseName;
payLen = rhs.payLen;
hasCounter = rhs.hasCounter;
#ifdef __APPLE__
// note: O_CREAT is only needed for mac, probably
mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0);
#else
mySemaphore.open(baseName.c_str(), O_RDWR);
#endif
if (!mySemaphore){
DEBUG_MSG(DLVL_FAIL, "Creating semaphore failed: %s", strerror(errno));
return;
}
myPage.init(rhs.myPage.name, rhs.myPage.len, rhs.myPage.master);
offsetOnPage = rhs.offsetOnPage;
}
///\brief Assignment operator
void sharedClient::operator=(const sharedClient &rhs){
countAsViewer = rhs.countAsViewer;
baseName = rhs.baseName;
payLen = rhs.payLen;
hasCounter = rhs.hasCounter;
#ifdef __APPLE__
// note: O_CREAT is only needed for mac, probably
mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0);
#else
mySemaphore.open(baseName.c_str(), O_RDWR);
#endif
if (!mySemaphore){
DEBUG_MSG(DLVL_FAIL, "Creating copy of semaphore %s failed: %s", baseName.c_str(), strerror(errno));
return;
}
myPage.init(rhs.myPage.name, rhs.myPage.len, rhs.myPage.master);
offsetOnPage = rhs.offsetOnPage;
}
///\brief SharedClient Constructor, allocates space on the correct page.
///\param name The basename of the server to connect to
///\param len The size of the payload to allocate
///\param withCounter Whether or not this payload has a counter
sharedClient::sharedClient(std::string name, int len, bool withCounter)
: baseName("/" + name), payLen(len), offsetOnPage(-1), hasCounter(withCounter){
countAsViewer = true;
#ifdef __APPLE__
// note: O_CREAT is only needed for mac, probably
mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0);
#else
mySemaphore.open(baseName.c_str(), O_RDWR);
#endif
if (!mySemaphore){
DEBUG_MSG(DLVL_FAIL, "Creating semaphore %s failed: %s", baseName.c_str(), strerror(errno));
return;
}
// Empty is used to compare for emptyness. This is not needed when the page uses a counter
char *empty = 0;
if (!hasCounter){
empty = (char *)malloc(payLen * sizeof(char));
if (!empty){
DEBUG_MSG(DLVL_FAIL, "Failed to allocate %u bytes for empty payload!", payLen);
return;
}
memset(empty, 0, payLen);
}
uint32_t attempts = 0;
while (offsetOnPage == -1 && (++attempts) < 20){
for (char i = 'A'; i <= 'Z'; i++){
myPage.init(baseName.substr(1) + i, (4096 << (i - 'A')), false, false);
if (!myPage.mapped){break;}
int offset = 0;
while (offset + payLen + (hasCounter ? 1 : 0) <= myPage.len){
if ((hasCounter && myPage.mapped[offset] == 0) ||
(!hasCounter && !memcmp(myPage.mapped + offset, empty, payLen))){
semGuard tmpGuard(&mySemaphore);
if ((hasCounter && myPage.mapped[offset] == 0) ||
(!hasCounter && !memcmp(myPage.mapped + offset, empty, payLen))){
offsetOnPage = offset;
if (hasCounter){
myPage.mapped[offset] = 1;
*((uint32_t *)(myPage.mapped + 1 + offset + len - 4)) = getpid();
HIGH_MSG("sharedClient received ID %d", offsetOnPage / (payLen + 1));
}
break;
}
}
offset += payLen + (hasCounter ? 1 : 0);
}
if (offsetOnPage != -1){break;}
}
if (offsetOnPage == -1){Util::wait(500);}
}
if (offsetOnPage == -1){
FAIL_MSG("Could not register on page for %s", baseName.c_str());
myPage.close();
}
if (empty){free(empty);}
}
///\brief The deconstructor
sharedClient::~sharedClient(){mySemaphore.close();}
///\brief Writes data to the shared data
void sharedClient::write(char *data, int len){
if (hasCounter){keepAlive();}
memcpy(myPage.mapped + offsetOnPage + (hasCounter ? 1 : 0), data, std::min(len, payLen));
}
///\brief Indicate that the process is done using this piece of memory, set the counter to finished
void sharedClient::finish(){
if (!myPage.mapped){return;}
if (!hasCounter){
DEBUG_MSG(DLVL_WARN, "Trying to time-out an element without counters");
myPage.close();
return;
}
semGuard tmpGuard(&mySemaphore);
myPage.mapped[offsetOnPage] = 126 | (countAsViewer ? 0x80 : 0);
HIGH_MSG("sharedClient finished ID %d", offsetOnPage / (payLen + 1));
myPage.close();
}
///\brief Re-initialize the counter
void sharedClient::keepAlive(){
if (!hasCounter){
DEBUG_MSG(DLVL_WARN, "Trying to keep-alive an element without counters");
return;
}
if (isAlive()){myPage.mapped[offsetOnPage] = (countAsViewer ? 0x81 : 0x01);}
}
bool sharedClient::isAlive(){
if (!hasCounter){return (myPage.mapped != 0);}
if (myPage.mapped && offsetOnPage >= 0){return (myPage.mapped[offsetOnPage] & 0x7F) < 60;}
return false;
}
///\brief Get a pointer to the data of this client
char *sharedClient::getData(){
if (!myPage.mapped){return 0;}
return (myPage.mapped + offsetOnPage + (hasCounter ? 1 : 0));
}
int sharedClient::getCounter(){
if (!hasCounter){return -1;}
if (!myPage.mapped){return 0;}
return *(myPage.mapped + offsetOnPage);
}
userConnection::userConnection(char *_data){
data = _data;
if (!data){WARN_MSG("userConnection created with null pointer!");}
}
unsigned long userConnection::getTrackId(size_t offset) const{
if (offset >= SIMUL_TRACKS){
WARN_MSG("Trying to get track id for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
return 0;
}
return Bit::btohl(data + (offset * 6));
}
void userConnection::setTrackId(size_t offset, unsigned long trackId) const{
if (offset >= SIMUL_TRACKS){
WARN_MSG("Trying to set track id for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
return;
}
Bit::htobl(data + (offset * 6), trackId);
}
unsigned long userConnection::getKeynum(size_t offset) const{
if (offset >= SIMUL_TRACKS){
WARN_MSG("Trying to get keynum for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
return 0;
}
return Bit::btohs(data + (offset * 6) + 4);
}
void userConnection::setKeynum(size_t offset, unsigned long keynum){
if (offset >= SIMUL_TRACKS){
WARN_MSG("Trying to set keynum for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
return;
}
Bit::htobs(data + (offset * 6) + 4, keynum);
}
}// namespace IPC }// namespace IPC

View file

@ -20,49 +20,6 @@
namespace IPC{ namespace IPC{
///\brief A class used for the exchange of statistics over shared memory.
class statExchange{
public:
statExchange(char *_data);
void now(long long int time);
long long int now();
void time(long time);
long time();
void lastSecond(long time);
long lastSecond();
void down(long long int bytes);
long long int down();
void up(long long int bytes);
long long int up();
void host(std::string name);
std::string host();
void streamName(std::string name);
std::string streamName();
void connector(std::string name);
std::string connector();
void crc(unsigned int sum);
char getSync();
void setSync(char s);
unsigned int crc();
uint32_t getPID();
std::string getSessId();
private:
///\brief The payload for the stat exchange
/// - 8 byte - now (timestamp of last statistics)
/// - 4 byte - time (duration of the current connection)
/// - 4 byte - lastSecond (last second of content viewed)
/// - 8 byte - down (Number of bytes received from peer)
/// - 8 byte - up (Number of bytes sent to peer)
/// - 16 byte - host (ip address of the peer)
/// - 100 byte - streamName (name of the stream peer is viewing)
/// - 20 byte - connector (name of the connector the peer is using)
/// - 4 byte - CRC32 of user agent (or zero if none)
/// - 1 byte sync (was seen by controller yes/no)
/// - (implicit 4 bytes: PID)
char *data;
};
///\brief A class used for the abstraction of semaphores ///\brief A class used for the abstraction of semaphores
class semaphore{ class semaphore{
public: public:
@ -75,6 +32,7 @@ namespace IPC{
void post(); void post();
void wait(); void wait();
bool tryWait(); bool tryWait();
bool tryWait(uint64_t ms);
bool tryWaitOneSecond(); bool tryWaitOneSecond();
void close(); void close();
void abandon(); void abandon();
@ -158,7 +116,7 @@ namespace IPC{
///\brief The name of the opened shared memory page ///\brief The name of the opened shared memory page
std::string name; std::string name;
///\brief The size in bytes of the opened shared memory page ///\brief The size in bytes of the opened shared memory page
long long int len; uint64_t len;
///\brief Whether this class should unlink the shared memory upon deletion or not ///\brief Whether this class should unlink the shared memory upon deletion or not
bool master; bool master;
///\brief A pointer to the payload of the page ///\brief A pointer to the payload of the page
@ -174,96 +132,4 @@ namespace IPC{
~sharedPage(); ~sharedPage();
}; };
#endif #endif
///\brief The server part of a server/client model for shared memory.
///
/// The server manages the shared memory pages, and allocates new pages when needed.
///
/// Pages are created with a basename + index, where index is in the range of 'A' - 'Z'
/// Each time a page is nearly full, the next page is created with a size double to the previous one.
///
/// Clients should allocate payLen bytes at a time, possibly with the addition of a counter.
/// If no such length can be allocated, the next page should be tried, and so on.
class sharedServer{
public:
sharedServer();
sharedServer(std::string name, int len, bool withCounter = false);
void init(std::string name, int len, bool withCounter = false);
~sharedServer();
void parseEach(void (*activeCallback)(char *data, size_t len, unsigned int id),
void (*disconCallback)(char *data, size_t len, unsigned int id) = 0);
char *getIndex(unsigned int id);
operator bool() const;
///\brief The amount of connected clients
unsigned int amount;
unsigned int connectedUsers;
void finishEach();
void abandon();
private:
bool isInUse(unsigned int id);
void newPage();
void deletePage();
///\brief The basename of the shared pages.
std::string baseName;
///\brief The length of each consecutive piece of payload
unsigned int payLen;
///\brief The set of sharedPage structures to manage the actual memory
std::deque<sharedPage> myPages;
///\brief A semaphore that is locked upon creation and deletion of the page, to ensure no new data is allocated during this step.
semaphore mySemaphore;
///\brief Whether the payload has a counter, if so, it is added in front of the payload
bool hasCounter;
};
///\brief The client part of a server/client model for shared memory.
///
/// The server manages the shared memory pages, and allocates new pages when needed.
///
/// Pages are created with a basename + index, where index is in the range of 'A' - 'Z'
/// Each time a page is nearly full, the next page is created with a size double to the previous one.
///
/// Clients should allocate payLen bytes at a time, possibly with the addition of a counter.
/// If no such length can be allocated, the next page should be tried, and so on.
class sharedClient{
public:
sharedClient();
sharedClient(const sharedClient &rhs);
sharedClient(std::string name, int len, bool withCounter = false);
void operator=(const sharedClient &rhs);
~sharedClient();
void write(char *data, int len);
void finish();
void keepAlive();
bool isAlive();
char *getData();
int getCounter();
bool countAsViewer;
private:
///\brief The basename of the shared pages.
std::string baseName;
///\brief The shared page this client has reserved a space on.
sharedPage myPage;
///\brief A semaphore that is locked upon trying to allocate space on a page
semaphore mySemaphore;
///\brief The size in bytes of the opened page
int payLen;
///\brief The offset of the payload reserved for this client within the opened page
int offsetOnPage;
///\brief Whether the payload has a counter, if so, it is added in front of the payload
bool hasCounter;
};
class userConnection{
public:
userConnection(char *_data);
unsigned long getTrackId(size_t offset) const;
void setTrackId(size_t offset, unsigned long trackId) const;
unsigned long getKeynum(size_t offset) const;
void setKeynum(size_t offset, unsigned long keynum);
private:
char *data;
};
}// namespace IPC }// namespace IPC

View file

@ -15,8 +15,10 @@ public:
SRTPReader(); SRTPReader();
int init(const std::string &cipher, const std::string &key, const std::string &salt); int init(const std::string &cipher, const std::string &key, const std::string &salt);
int shutdown(); int shutdown();
int unprotectRtp(uint8_t *data, int *nbytes); /* `nbytes` should contain the number of bytes in `data`. On success `nbytes` will hold the number of bytes of the decoded RTP packet. */ int unprotectRtp(uint8_t *data, int *nbytes); /* `nbytes` should contain the number of bytes in `data`. On success `nbytes`
int unprotectRtcp(uint8_t *data, int *nbytes); /* `nbytes` should contains the number of bytes in `data`. On success `nbytes` will hold the number of bytes the decoded RTCP packet. */ will hold the number of bytes of the decoded RTP packet. */
int unprotectRtcp(uint8_t *data, int *nbytes); /* `nbytes` should contains the number of bytes in `data`. On success `nbytes`
will hold the number of bytes the decoded RTCP packet. */
private: private:
srtp_t session; srtp_t session;

View file

@ -253,20 +253,6 @@ JSON::Value Util::getGlobalConfig(const std::string &optionName){
} }
} }
DTSC::Meta Util::getStreamMeta(const std::string &streamname){
DTSC::Meta ret;
char pageId[NAME_BUFFER_SIZE];
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamname.c_str());
IPC::sharedPage mPage(pageId, DEFAULT_STRM_PAGE_SIZE);
if (!mPage.mapped){
FAIL_MSG("Could not connect to metadata for %s", streamname.c_str());
return ret;
}
DTSC::Packet tmpMeta(mPage.mapped, mPage.len, true);
if (tmpMeta.getVersion()){ret.reinit(tmpMeta);}
return ret;
}
/// Checks if the given streamname has an active input serving it. Returns true if this is the case. /// Checks if the given streamname has an active input serving it. Returns true if this is the case.
/// Assumes the streamname has already been through sanitizeName()! /// Assumes the streamname has already been through sanitizeName()!
bool Util::streamAlive(std::string &streamname){ bool Util::streamAlive(std::string &streamname){
@ -696,12 +682,16 @@ DTSC::Scan Util::DTSCShmReader::getScan(){
return DTSC::Scan(rAcc.getPointer("dtsc_data"), rAcc.getSize("dtsc_data")); return DTSC::Scan(rAcc.getPointer("dtsc_data"), rAcc.getSize("dtsc_data"));
} }
std::set<size_t> Util::findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, /*LTS-START*/
const std::string &trackVal, const std::string &UA){ /// Selects a specific track or set of tracks of the given trackType, using trackVal to decide.
/// trackVal may be a comma-separated list of numbers, codecs or the word "all" or an asterisk.
/// 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> result; std::set<size_t> result;
if (!trackVal.size() || trackVal == "0" || trackVal == "-1" || trackVal == "none"){ if (!trackVal.size()){return result;}
return result; if (trackVal == "-1" | trackVal == "none"){return result;}// don't select anything in particular
}// don't select anything in particular
if (trackVal.find(',') != std::string::npos){ if (trackVal.find(',') != std::string::npos){
// Comma-separated list, recurse. // Comma-separated list, recurse.
std::stringstream ss(trackVal); std::stringstream ss(trackVal);
@ -712,36 +702,27 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const JSON::Value &capa,
} }
return result; return result;
} }
{ size_t idx = JSON::Value(trackVal).asInt();
size_t trackNo = JSON::Value(trackVal).asInt(); if (trackVal == JSON::Value(idx).asString()){
if (trackVal == JSON::Value((uint64_t)trackNo).asString()){ if (!M.trackValid(idx)){
// It's an integer number WARN_MSG("Track %zu does not exist in stream, cannot select", idx);
if (!M.tracks.count(trackNo)){
INFO_MSG("Track %zd does not exist in stream, cannot select", trackNo);
return result;
}
const DTSC::Track &Trk = M.tracks.at(trackNo);
if (Trk.type != trackType && Trk.codec != trackType){
INFO_MSG("Track %zd is not %s (%s/%s), cannot select", trackNo, trackType.c_str(),
Trk.type.c_str(), Trk.codec.c_str());
return result;
}
INFO_MSG("Selecting %s track %zd (%s/%s)", trackType.c_str(), trackNo, Trk.type.c_str(),
Trk.codec.c_str());
result.insert(trackNo);
return result; return result;
} }
if (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;
}
result.insert(idx);
return result;
} }
std::string trackLow = trackVal; std::string trackLow = trackVal;
Util::stringToLower(trackLow); Util::stringToLower(trackLow);
if (trackLow == "all" || trackLow == "*"){ if (trackLow == "all" || trackLow == "*"){
// select all tracks of this type // select all tracks of this type
std::set<size_t> validTracks = getSupportedTracks(M, capa); std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (M.getType(*it) == trackType || M.getCodec(*it) == trackType){result.insert(*it);}
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){
result.insert(*it);
}
} }
return result; return result;
} }
@ -960,27 +941,12 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const JSON::Value &capa,
// attempt to do language/codec matching // attempt to do language/codec matching
// convert 2-character language codes into 3-character language codes // convert 2-character language codes into 3-character language codes
if (trackLow.size() == 2){trackLow = Encodings::ISO639::twoToThree(trackLow);} if (trackLow.size() == 2){trackLow = Encodings::ISO639::twoToThree(trackLow);}
std::set<size_t> validTracks = getSupportedTracks(M, capa); std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ std::string codecLow = M.getCodec(*it);
std::string codecLow = Trk.codec;
Util::stringToLower(codecLow); Util::stringToLower(codecLow);
if (Trk.lang == trackLow || trackLow == codecLow){result.insert(*it);} if (M.getLang(*it) == trackLow || trackLow == codecLow){result.insert(*it);}
if (!trackType.size() || trackType == "video"){
unsigned int resX, resY;
if (trackLow == "720p" && Trk.width == 1280 && Trk.height == 720){result.insert(*it);}
if (trackLow == "1080p" && Trk.width == 1920 && Trk.height == 1080){result.insert(*it);}
if (trackLow == "1440p" && Trk.width == 2560 && Trk.height == 1440){result.insert(*it);}
if (trackLow == "2k" && Trk.width == 2048 && Trk.height == 1080){result.insert(*it);}
if (trackLow == "4k" && Trk.width == 3840 && Trk.height == 2160){result.insert(*it);}
if (trackLow == "5k" && Trk.width == 5120 && Trk.height == 2880){result.insert(*it);}
if (trackLow == "8k" && Trk.width == 7680 && Trk.height == 4320){result.insert(*it);}
// match "XxY" format
if (sscanf(trackLow.c_str(), "%ux%u", &resX, &resY) == 2){
if (Trk.width == resX && Trk.height == resY){result.insert(*it);}
}
}
} }
} }
return result; return result;
@ -996,14 +962,19 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::string &track
std::set<size_t> Util::getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa, std::set<size_t> Util::getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa,
const std::string &type, const std::string &UA){ const std::string &type, const std::string &UA){
std::set<size_t> validTracks; std::set<size_t> validTracks = M.getValidTracks();
for (std::map<unsigned int, DTSC::Track>::const_iterator it = M.tracks.begin(); it != M.tracks.end(); it++){
const DTSC::Track &Trk = it->second; std::set<size_t> toRemove;
if (type != "" && type != Trk.type){continue;} for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
// Remove unrequested tracks
if (type != "" && type != M.getType(*it)){
toRemove.insert(*it);
continue;
}
// Remove tracks for which we don't have codec support // Remove tracks for which we don't have codec support
if (capa.isMember("codecs")){ if (capa.isMember("codecs")){
std::string codec = Trk.codec; std::string codec = M.getCodec(*it);
std::string type = Trk.type; std::string type = M.getType(*it);
bool found = false; bool found = false;
jsonForEachConst(capa["codecs"], itb){ jsonForEachConst(capa["codecs"], itb){
jsonForEachConst(*itb, itc){ jsonForEachConst(*itb, itc){
@ -1040,11 +1011,30 @@ std::set<size_t> Util::getSupportedTracks(const DTSC::Meta &M, const JSON::Value
if (found){break;} if (found){break;}
} }
if (!found){ if (!found){
HIGH_MSG("Track %u with codec %s not supported!", it->first, codec.c_str()); HIGH_MSG("Track %zu with codec %s not supported!", *it, codec.c_str());
toRemove.insert(*it);
continue; continue;
} }
} }
validTracks.insert(it->first); // Remove encrypted tracks if not supported
if (M.getEncryption(*it) != ""){
std::string encryptionType = M.getEncryption(*it);
encryptionType = encryptionType.substr(0, encryptionType.find('/'));
bool found = false;
jsonForEach(capa["encryption"], itb){
if (itb->asStringRef() == encryptionType){
found = true;
break;
}
}
if (!found){
INFO_MSG("Track %zu with encryption type %s not supported!", *it, encryptionType.c_str());
toRemove.insert(*it);
}
}
}
for (std::set<size_t>::iterator it = toRemove.begin(); it != toRemove.end(); it++){
validTracks.erase(*it);
} }
return validTracks; return validTracks;
} }
@ -1153,9 +1143,8 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
++shift; ++shift;
} }
for (std::set<size_t>::iterator itd = result.begin(); itd != result.end(); itd++){ for (std::set<size_t>::iterator itd = result.begin(); itd != result.end(); itd++){
const DTSC::Track &Trk = M.tracks.at(*itd); if ((!byType && M.getCodec(*itd) == strRef.substr(shift)) ||
if ((!byType && Trk.codec == strRef.substr(shift)) || (byType && M.getType(*itd) == strRef.substr(shift)) || strRef.substr(shift) == "*"){
(byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){
// user-agent-check // user-agent-check
bool problems = false; bool problems = false;
if (capa.isMember("exceptions") && capa["exceptions"].isObject() && if (capa.isMember("exceptions") && capa["exceptions"].isObject() &&
@ -1208,9 +1197,8 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
++shift; ++shift;
} }
for (std::set<size_t>::iterator itd = result.begin(); itd != result.end(); itd++){ for (std::set<size_t>::iterator itd = result.begin(); itd != result.end(); itd++){
const DTSC::Track &Trk = M.tracks.at(*itd); if ((!byType && M.getCodec(*itd) == strRef.substr(shift)) ||
if ((!byType && Trk.codec == strRef.substr(shift)) || (byType && M.getType(*itd) == strRef.substr(shift)) || strRef.substr(shift) == "*"){
(byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){
// user-agent-check // user-agent-check
bool problems = false; bool problems = false;
if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){
@ -1242,12 +1230,11 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
++shift; ++shift;
} }
if (found && !multiSel){continue;} if (found && !multiSel){continue;}
if (M.live){ if (M.getLive()){
for (std::set<size_t>::reverse_iterator trit = validTracks.rbegin(); for (std::set<size_t>::reverse_iterator trit = validTracks.rbegin();
trit != validTracks.rend(); trit++){ trit != validTracks.rend(); trit++){
const DTSC::Track &Trk = M.tracks.at(*trit); if ((!byType && M.getCodec(*trit) == strRef.substr(shift)) ||
if ((!byType && Trk.codec == strRef.substr(shift)) || (byType && M.getType(*trit) == strRef.substr(shift)) || strRef.substr(shift) == "*"){
(byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){
// user-agent-check // user-agent-check
bool problems = false; bool problems = false;
if (capa.isMember("exceptions") && capa["exceptions"].isObject() && if (capa.isMember("exceptions") && capa["exceptions"].isObject() &&
@ -1259,12 +1246,15 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
} }
} }
} }
// if (!allowBFrames && M.hasBFrames(*trit)){problems = true;} if (!allowBFrames && M.hasBFrames(*trit)){problems = true;}
if (problems){break;} if (problems){break;}
/*LTS-START*/ /*LTS-START*/
if (noSelAudio && Trk.type == "audio"){continue;} if (noSelAudio && M.getType(*trit) == "audio"){continue;}
if (noSelVideo && Trk.type == "video"){continue;} if (noSelVideo && M.getType(*trit) == "video"){continue;}
if (noSelSub && (Trk.type == "subtitle" || Trk.codec == "subtitle")){continue;} if (noSelSub &&
(M.getType(*trit) == "subtitle" || M.getCodec(*trit) == "subtitle")){
continue;
}
/*LTS-END*/ /*LTS-END*/
result.insert(*trit); result.insert(*trit);
found = true; found = true;
@ -1273,9 +1263,8 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
} }
}else{ }else{
for (std::set<size_t>::iterator trit = validTracks.begin(); trit != validTracks.end(); trit++){ for (std::set<size_t>::iterator trit = validTracks.begin(); trit != validTracks.end(); trit++){
const DTSC::Track &Trk = M.tracks.at(*trit); if ((!byType && M.getCodec(*trit) == strRef.substr(shift)) ||
if ((!byType && Trk.codec == strRef.substr(shift)) || (byType && M.getType(*trit) == strRef.substr(shift)) || strRef.substr(shift) == "*"){
(byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){
// user-agent-check // user-agent-check
bool problems = false; bool problems = false;
if (capa.isMember("exceptions") && capa["exceptions"].isObject() && if (capa.isMember("exceptions") && capa["exceptions"].isObject() &&
@ -1287,12 +1276,15 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
} }
} }
} }
// if (!allowBFrames && M.hasBFrames(*trit)){problems = true;} if (!allowBFrames && M.hasBFrames(*trit)){problems = true;}
if (problems){break;} if (problems){break;}
/*LTS-START*/ /*LTS-START*/
if (noSelAudio && Trk.type == "audio"){continue;} if (noSelAudio && M.getType(*trit) == "audio"){continue;}
if (noSelVideo && Trk.type == "video"){continue;} if (noSelVideo && M.getType(*trit) == "video"){continue;}
if (noSelSub && (Trk.type == "subtitle" || Trk.type == "subtitle")){continue;} if (noSelSub &&
(M.getType(*trit) == "subtitle" || M.getCodec(*trit) == "subtitle")){
continue;
}
/*LTS-END*/ /*LTS-END*/
result.insert(*trit); result.insert(*trit);
found = true; found = true;

View file

@ -9,8 +9,6 @@
#include "util.h" #include "util.h"
#include <string> #include <string>
const JSON::Value empty;
namespace Util{ namespace Util{
void streamVariables(std::string &str, const std::string &streamname, const std::string &source = ""); void streamVariables(std::string &str, const std::string &streamname, const std::string &source = "");
std::string getTmpFolder(); std::string getTmpFolder();
@ -24,20 +22,17 @@ namespace Util{
JSON::Value getStreamConfig(const std::string &streamname); JSON::Value getStreamConfig(const std::string &streamname);
JSON::Value getGlobalConfig(const std::string &optionName); JSON::Value getGlobalConfig(const std::string &optionName);
JSON::Value getInputBySource(const std::string &filename, bool isProvider = false); JSON::Value getInputBySource(const std::string &filename, bool isProvider = false);
DTSC::Meta getStreamMeta(const std::string &streamname);
uint8_t getStreamStatus(const std::string &streamname); uint8_t getStreamStatus(const std::string &streamname);
bool checkException(const JSON::Value &ex, const std::string &useragent); bool checkException(const JSON::Value &ex, const std::string &useragent);
std::string codecString(const std::string &codec, const std::string &initData = ""); std::string codecString(const std::string &codec, const std::string &initData = "");
std::set<size_t> getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa = empty, std::set<size_t> getSupportedTracks(const DTSC::Meta &M, JSON::Value &capa,
const std::string &type = "", const std::string &UA = ""); const std::string &type = "", const std::string &UA = "");
std::set<size_t> findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, std::set<size_t> findTracks(const DTSC::Meta &M, const std::string &trackType, const std::string &trackVal);
const std::string &trackVal, const std::string &UA = "");
std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::string &trackSelector = "", std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::string &trackSelector = "",
const JSON::Value &capa = empty, const std::string &UA = ""); JSON::Value capa = JSON::Value(), const std::string &UA = "");
std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::map<std::string, std::string> &targetParams, std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::map<std::string, std::string> &targetParams,
const JSON::Value &capa = empty, const std::string &UA = "", JSON::Value capa = JSON::Value(), const std::string &UA = "");
uint64_t seekTarget = 0);
class DTSCShmReader{ class DTSCShmReader{
public: public:

View file

@ -233,7 +233,7 @@ int stun_compute_hmac_sha1(uint8_t *message, uint32_t nbytes, std::string key, u
goto error; goto error;
} }
DONTEVEN_MSG("Calculating hmac-sha1 with key `%s` with size %zu over %zu bytes of data.", DONTEVEN_MSG("Calculating hmac-sha1 with key `%s` with size %zu over %" PRIu32 " bytes of data.",
key.c_str(), key.size(), nbytes); key.c_str(), key.size(), nbytes);
r = mbedtls_md_hmac_starts(&md_ctx, (const unsigned char *)key.c_str(), key.size()); r = mbedtls_md_hmac_starts(&md_ctx, (const unsigned char *)key.c_str(), key.size());

View file

@ -202,8 +202,10 @@ public:
/* write header and finalize. call for each stun message */ /* write header and finalize. call for each stun message */
int begin(StunMessage &msg, int begin(StunMessage &msg,
uint8_t paddingByte = 0x00); /* I've added the padding byte here so that we can use the uint8_t paddingByte = 0x00); /* I've added the padding byte here so that we can use the
examples that can be found here https://tools.ietf.org/html/rfc5769#section-2.2 examples that can be found here
as they use 0x20 or 0x00 as the padding byte which is correct as you are free to use w/e padding byte you want. */ https://tools.ietf.org/html/rfc5769#section-2.2 as they
use 0x20 or 0x00 as the padding byte which is correct as
you are free to use w/e padding byte you want. */
int end(); int end();
/* write attributes */ /* write attributes */
@ -213,7 +215,9 @@ public:
int writeUsername(const std::string &username); int writeUsername(const std::string &username);
int writeSoftware(const std::string &software); int writeSoftware(const std::string &software);
int writeMessageIntegrity(const std::string &password); /* When using WebRtc this is the ice-upwd of the other agent. */ int writeMessageIntegrity(const std::string &password); /* When using WebRtc this is the ice-upwd of the other agent. */
int writeFingerprint(); /* Must be the last attribute in the message. When adding a fingerprint, make sure that it is added after the message-integrity (when you also use a message-integrity). */ int writeFingerprint(); /* Must be the last attribute in the message. When adding a fingerprint,
make sure that it is added after the message-integrity (when you also
use a message-integrity). */
/* get buffer */ /* get buffer */
uint8_t *getBufferPtr(); uint8_t *getBufferPtr();

View file

@ -140,11 +140,10 @@ namespace Triggers{
return doTrigger(type, empty, streamName, true, usually_empty, paramsCB, extraParam); return doTrigger(type, empty, streamName, true, usually_empty, paramsCB, extraParam);
} }
///\brief handles triggers for a specific trigger event type, with a payload, for a specified stream, and/or server-wide ///\brief handles triggers for a specific trigger event type, with a payload, for a specified
///\param type Trigger event type. /// stream, and/or server-wide \param type Trigger event type. \param payload Trigger
///\param payload Trigger type-specific data /// type-specific data \param streamName The name of a stream. \returns Boolean, false if further
///\param streamName The name of a stream. /// processing should be aborted.
///\returns Boolean, false if further processing should be aborted.
/// calls doTrigger with dryRun set to false /// calls doTrigger with dryRun set to false
bool doTrigger(const std::string &type, const std::string &payload, const std::string &streamName){ bool doTrigger(const std::string &type, const std::string &payload, const std::string &streamName){
usually_empty.clear(); usually_empty.clear();
@ -158,14 +157,17 @@ namespace Triggers{
///\param dryRun determines the mode of operation for this function ///\param dryRun determines the mode of operation for this function
///\param response Returns the last received response by reference ///\param response Returns the last received response by reference
///\returns Boolean, false if further processing should be aborted ///\returns Boolean, false if further processing should be aborted
/// This function attempts to open and parse a shared memory page with the config for a trigger event type, in order to parse the triggers /// This function attempts to open and parse a shared memory page with the config for a trigger
/// defined for that trigger event type. /// event type, in order to parse the triggers defined for that trigger event type. The function
/// The function can be used for two separate purposes, determined by the value of dryRun /// can be used for two separate purposes, determined by the value of dryRun
///-if this function is called with dryRun==true (for example, from a handleTrigger function), the return value will be true, if at least one ///-if this function is called with dryRun==true (for example, from a handleTrigger function), the
/// trigger should be handled for the requested type/stream. /// return value will be true, if at least one trigger should be handled for the requested
/// this can be used to make sure a payload is only generated if at least one trigger should be handled. /// type/stream.
///-if this function is called with dryRun==false (for example, from one of the overloaded doTrigger functions), handleTrigger is called for /// this can be used to make sure a payload is only generated if at least one trigger should be
/// all configured triggers. In that case, the return value does not matter, it will probably be false in all cases. /// handled.
///-if this function is called with dryRun==false (for example, from one of the overloaded
/// doTrigger functions), handleTrigger is called for all configured triggers. In that case, the
/// return value does not matter, it will probably be false in all cases.
bool doTrigger(const std::string &type, const std::string &payload, const std::string &streamName, bool doTrigger(const std::string &type, const std::string &payload, const std::string &streamName,
bool dryRun, std::string &response, bool paramsCB(const char *, const void *), bool dryRun, std::string &response, bool paramsCB(const char *, const void *),
const void *extraParam){ const void *extraParam){

View file

@ -30,7 +30,38 @@
std::set<unsigned int> pmt_pids; std::set<unsigned int> pmt_pids;
std::map<unsigned int, std::string> stream_pids; std::map<unsigned int, std::string> stream_pids;
/// A standard Program Association Table, as generated by FFMPEG.
/// Seems to be independent of the stream.
// 0x47 = sync byte
// 0x4000 = transport error(1) = 0, payload unit start(1) = 1, priority(1) = 0, PID(13) = 0
// 0x10 = transportscrambling(2) = 0, adaptation(2) = 1, continuity(4) = 0
// 0x00 = pointer = 0
// 0x00 = table ID = 0 = PAT
// 0xB00D = section syntax(1) = 1, 0(1)=0, reserved(2) = 3, section_len(12) = 13
// 0x0001 = transport stream id = 1
// 0xC1 = reserved(2) = 3, version(5)=0, curr_next_indi(1) = 1
// 0x00 = section_number = 0
// 0x00 = last_section_no = 0
// 0x0001 = ProgNo = 1
// 0xF000 = reserved(3) = 7, network pid = 4096
// 0x2AB104B2 = CRC32
namespace TS{ namespace TS{
char PAT[188] ={
0x47, 0x40, 0x00, 0x10, 0x00, 0x00, 0xB0, 0x0D, 0x00, 0x01, 0xC1, 0x00, 0x00, 0x00, 0x01,
0xF0, 0x00, 0x2A, 0xB1, 0x04, 0xB2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
/// This constructor creates an empty Packet, ready for use for either reading or writing. /// This constructor creates an empty Packet, ready for use for either reading or writing.
/// All this constructor does is call Packet::clear(). /// All this constructor does is call Packet::clear().
Packet::Packet(){ Packet::Packet(){
@ -524,6 +555,35 @@ namespace TS{
} }
return tmpStr; return tmpStr;
} }
/// Generates a PES Lead-in for a meta frame.
/// Prepends the lead-in to variable toSend, assumes toSend's length is all other data.
/// \param len The length of this frame.
/// \param PTS The timestamp of the frame.
std::string &Packet::getPESMetaLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps){
if (bps >= 50){
len += 3;
}else{
bps = 0;
}
static std::string tmpStr;
tmpStr.clear();
tmpStr.reserve(20);
len += 8;
tmpStr.append("\000\000\001\374", 4);
tmpStr += (char)((len & 0xFF00) >> 8); // PES PacketLength
tmpStr += (char)(len & 0x00FF); // PES PacketLength (Cont)
tmpStr += (char)0x84; // isAligned
tmpStr += (char)(0x80 | (bps ? 0x10 : 0)); // PTS/DTS + Flags
tmpStr += (char)(5 + (bps ? 3 : 0)); // PESHeaderDataLength
encodePESTimestamp(tmpStr, 0x20, PTS);
if (bps){
char rate_buf[3];
Bit::htob24(rate_buf, (bps / 50) | 0x800001);
tmpStr.append(rate_buf, 3);
}
return tmpStr;
}
// END PES FUNCTIONS // END PES FUNCTIONS
/// Fills the free bytes of the Packet. /// Fills the free bytes of the Packet.
@ -1108,18 +1168,20 @@ namespace TS{
///\param selectedTracks tracks to include in PMT creation ///\param selectedTracks tracks to include in PMT creation
///\param myMeta ///\param myMeta
///\returns character pointer to a static 188B TS packet ///\returns character pointer to a static 188B TS packet
const char *createPMT(std::set<unsigned long> &selectedTracks, DTSC::Meta &myMeta, int contCounter){ const char *createPMT(std::set<unsigned long> &selectedTracks, const DTSC::Meta &M, int contCounter){
static ProgramMappingTable PMT; static ProgramMappingTable PMT;
PMT.setPID(4096); PMT.setPID(4096);
PMT.setTableId(2); PMT.setTableId(2);
// section length met 2 tracks: 0xB017 // section length met 2 tracks: 0xB017
int sectionLen = 0; int sectionLen = 0;
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
std::string codec = M.getCodec(*it);
sectionLen += 5; sectionLen += 5;
if (myMeta.tracks[*it].codec == "ID3"){sectionLen += myMeta.tracks[*it].init.size();} if (codec == "ID3" || codec == "RAW"){sectionLen += M.getInit(*it).size();}
if (myMeta.tracks[*it].codec == "AAC"){ if (codec == "AAC"){
sectionLen += 4; // aac descriptor sectionLen += 4; // aac descriptor
if (myMeta.tracks[*it].lang.size() == 3 && myMeta.tracks[*it].lang != "und"){ std::string lang = M.getLang(*it);
if (lang.size() == 3 && lang != "und"){
sectionLen += 6; // language descriptor sectionLen += 6; // language descriptor
} }
} }
@ -1133,42 +1195,51 @@ namespace TS{
PMT.setContinuityCounter(contCounter); PMT.setContinuityCounter(contCounter);
int vidTrack = -1; int vidTrack = -1;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
if (myMeta.tracks[*it].type == "video"){ if (M.getType(*it) == "video"){
vidTrack = *it; vidTrack = *it;
break; break;
} }
} }
if (vidTrack == -1){vidTrack = *(selectedTracks.begin());} if (vidTrack == -1){vidTrack = *(selectedTracks.begin());}
PMT.setPCRPID(255 + vidTrack); size_t pcrPid = M.getID(vidTrack);
if (pcrPid < 255){pcrPid += 255;}
PMT.setPCRPID(pcrPid);
PMT.setProgramInfoLength(0); PMT.setProgramInfoLength(0);
short id = 0;
ProgramMappingEntry entry = PMT.getEntry(0); ProgramMappingEntry entry = PMT.getEntry(0);
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
entry.setElementaryPid(255 + *it); std::string codec = M.getCodec(*it);
size_t pkgId = M.getID(*it);
if (pkgId < 255){pkgId += 255;}
entry.setElementaryPid(pkgId);
entry.setESInfo(""); entry.setESInfo("");
if (myMeta.tracks[*it].codec == "H264"){ if (codec == "H264"){
entry.setStreamType(0x1B); entry.setStreamType(0x1B);
}else if (myMeta.tracks[*it].codec == "HEVC"){ }else if (codec == "HEVC"){
entry.setStreamType(0x24); entry.setStreamType(0x24);
}else if (myMeta.tracks[*it].codec == "MPEG2"){ }else if (codec == "MPEG2"){
entry.setStreamType(0x02); entry.setStreamType(0x02);
}else if (myMeta.tracks[*it].codec == "AAC"){ }else if (codec == "AAC"){
entry.setStreamType(0x0F); entry.setStreamType(0x0F);
std::string aac_info("\174\002\121\000", 4); // AAC descriptor: AAC Level 2. Hardcoded, because... what are AAC levels, anyway? std::string aac_info("\174\002\121\000",
4); // AAC descriptor: AAC Level 2. Hardcoded, because... what are AAC levels, anyway?
// language code ddescriptor // language code ddescriptor
if (myMeta.tracks[*it].lang.size() == 3 && myMeta.tracks[*it].lang != "und"){ std::string lang = M.getLang(*it);
if (lang.size() == 3 && lang != "und"){
aac_info.append("\012\004", 2); aac_info.append("\012\004", 2);
aac_info.append(myMeta.tracks[*it].lang); aac_info.append(lang);
aac_info.append("\000", 1); aac_info.append("\000", 1);
} }
entry.setESInfo(aac_info); entry.setESInfo(aac_info);
}else if (myMeta.tracks[*it].codec == "MP3" || myMeta.tracks[*it].codec == "MP2"){ }else if (codec == "MP3" || codec == "MP2"){
entry.setStreamType(0x03); entry.setStreamType(0x03);
}else if (myMeta.tracks[*it].codec == "AC3"){ }else if (codec == "AC3"){
entry.setStreamType(0x81); entry.setStreamType(0x81);
}else if (myMeta.tracks[*it].codec == "ID3"){ }else if (codec == "ID3"){
entry.setStreamType(0x15); entry.setStreamType(0x15);
entry.setESInfo(myMeta.tracks[*it].init); entry.setESInfo(M.getInit(*it));
}else if (codec == "RAW"){
entry.setStreamType(0x06);
entry.setESInfo(M.getInit(*it));
} }
entry.advance(); entry.advance();
} }
@ -1365,7 +1436,9 @@ namespace TS{
getOffset() + getSectionLength(); getOffset() + getSectionLength();
unsigned int newVal; // this will hold the CRC32 value; unsigned int newVal; // this will hold the CRC32 value;
unsigned int pidLoc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 1; unsigned int pidLoc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 1;
newVal = checksum::crc32(-1, strBuf + pidLoc, loc - pidLoc); // calculating checksum over all the fields from table ID to the last stream element newVal = checksum::crc32(-1, strBuf + pidLoc,
loc - pidLoc); // calculating checksum over all the fields from table
// ID to the last stream element
updPos(188); updPos(188);
strBuf[loc + 3] = (newVal >> 24) & 0xFF; strBuf[loc + 3] = (newVal >> 24) & 0xFF;
strBuf[loc + 2] = (newVal >> 16) & 0xFF; strBuf[loc + 2] = (newVal >> 16) & 0xFF;

View file

@ -75,6 +75,7 @@ namespace TS{
static std::string &getPESVideoLeadIn(unsigned int len, unsigned long long PTS, static std::string &getPESVideoLeadIn(unsigned int len, unsigned long long PTS,
unsigned long long offset, bool isAligned, uint64_t bps = 0); unsigned long long offset, bool isAligned, uint64_t bps = 0);
static std::string &getPESAudioLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); static std::string &getPESAudioLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0);
static std::string &getPESMetaLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0);
// Printers and writers // Printers and writers
std::string toPrettyString(size_t indent = 0, int detailLevel = 3) const; std::string toPrettyString(size_t indent = 0, int detailLevel = 3) const;
@ -242,37 +243,9 @@ namespace TS{
return std::string(StandardHeader, 7); return std::string(StandardHeader, 7);
} }
/// A standard Program Association Table, as generated by FFMPEG. extern char PAT[188];
/// Seems to be independent of the stream.
// 0x47 = sync byte
// 0x4000 = transport error(1) = 0, payload unit start(1) = 1, priority(1) = 0, PID(13) = 0
// 0x10 = transportscrambling(2) = 0, adaptation(2) = 1, continuity(4) = 0
// 0x00 = pointer = 0
// 0x00 = table ID = 0 = PAT
// 0xB00D = section syntax(1) = 1, 0(1)=0, reserved(2) = 3, section_len(12) = 13
// 0x0001 = transport stream id = 1
// 0xC1 = reserved(2) = 3, version(5)=0, curr_next_indi(1) = 1
// 0x00 = section_number = 0
// 0x00 = last_section_no = 0
// 0x0001 = ProgNo = 1
// 0xF000 = reserved(3) = 7, network pid = 4096
// 0x2AB104B2 = CRC32
static char PAT[188] ={
0x47, 0x40, 0x00, 0x10, 0x00, 0x00, 0xB0, 0x0D, 0x00, 0x01, 0xC1, 0x00, 0x00, 0x00, 0x01,
0xF0, 0x00, 0x2A, 0xB1, 0x04, 0xB2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
const char *createPMT(std::set<unsigned long> &selectedTracks, DTSC::Meta &myMeta, int contCounter = 0); const char *createPMT(std::set<unsigned long> &selectedTracks, const DTSC::Meta &M, int contCounter = 0);
const char *createSDT(const std::string &streamName, int contCounter = 0); const char *createSDT(const std::string &streamName, int contCounter = 0);
}// namespace TS }// namespace TS

View file

@ -203,8 +203,9 @@ namespace TS{
case ID3: case ID3:
case MP2: case MP2:
case MPEG2: case MPEG2:
case META:
pidToCodec[pid] = sType; pidToCodec[pid] = sType;
if (sType == ID3){ if (sType == ID3 || sType == META){
metaInit[pid] = std::string(entry.getESInfo(), entry.getESInfoLength()); metaInit[pid] = std::string(entry.getESInfo(), entry.getESInfoLength());
} }
break; break;
@ -536,7 +537,7 @@ namespace TS{
} }
} }
} }
if (thisCodec == ID3 || thisCodec == AC3 || thisCodec == MP2){ if (thisCodec == ID3 || thisCodec == AC3 || thisCodec == MP2 || thisCodec == META){
out.push_back(DTSC::Packet()); out.push_back(DTSC::Packet());
out.back().genericFill(timeStamp, timeOffset, tid, pesPayload, realPayloadSize, bPos, 0); out.back().genericFill(timeStamp, timeOffset, tid, pesPayload, realPayloadSize, bPos, 0);
if (thisCodec == MP2 && !mp2Hdr.count(tid)){ if (thisCodec == MP2 && !mp2Hdr.count(tid)){
@ -607,7 +608,7 @@ namespace TS{
DTSC::Packet &bp = buildPacket[tid]; DTSC::Packet &bp = buildPacket[tid];
// Check if this is a keyframe // Check if this is a keyframe
parseNal(tid, pesPayload, nextPtr, isKeyFrame); parseNal(tid, pesPayload, pesPayload + nalSize, isKeyFrame);
// If yes, set the keyframe flag // If yes, set the keyframe flag
if (isKeyFrame){bp.setKeyFrame(true);} if (isKeyFrame){bp.setKeyFrame(true);}
@ -651,10 +652,10 @@ namespace TS{
while (nextPtr < pesEnd && nalno < 8){ while (nextPtr < pesEnd && nalno < 8){
if (!nextPtr){nextPtr = pesEnd;} if (!nextPtr){nextPtr = pesEnd;}
// Calculate size of NAL unit, removing null bytes from the end // Calculate size of NAL unit, removing null bytes from the end
nalu::nalEndPosition(pesPayload, nextPtr - pesPayload); uint32_t nalSize = nalu::nalEndPosition(pesPayload, nextPtr - pesPayload) - pesPayload;
// Check if this is a keyframe // Check if this is a keyframe
parseNal(tid, pesPayload, nextPtr, isKeyFrame); parseNal(tid, pesPayload, pesPayload + nalSize, isKeyFrame);
++nalno; ++nalno;
if (((nextPtr - pesPayload) + 3) >= realPayloadSize){break;}// end of the loop if (((nextPtr - pesPayload) + 3) >= realPayloadSize){break;}// end of the loop
@ -667,7 +668,7 @@ namespace TS{
} }
} }
void Stream::getPacket(size_t tid, DTSC::Packet &pack){ void Stream::getPacket(size_t tid, DTSC::Packet &pack, size_t mappedAs){
tthread::lock_guard<tthread::recursive_mutex> guard(tMutex); tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
pack.null(); pack.null();
if (!hasPacket(tid)){ if (!hasPacket(tid)){
@ -687,7 +688,7 @@ namespace TS{
return; return;
} }
pack = outPackets[tid].front(); pack = DTSC::Packet(outPackets[tid].front(), mappedAs);
outPackets[tid].pop_front(); outPackets[tid].pop_front();
if (!outPackets[tid].size()){outPackets.erase(tid);} if (!outPackets[tid].size()){outPackets.erase(tid);}
@ -825,14 +826,18 @@ namespace TS{
void Stream::initializeMetadata(DTSC::Meta &meta, size_t tid, size_t mappingId){ void Stream::initializeMetadata(DTSC::Meta &meta, size_t tid, size_t mappingId){
tthread::lock_guard<tthread::recursive_mutex> guard(tMutex); tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
size_t mId = mappingId;
for (std::map<size_t, uint32_t>::const_iterator it = pidToCodec.begin(); it != pidToCodec.end(); it++){ for (std::map<size_t, uint32_t>::const_iterator it = pidToCodec.begin(); it != pidToCodec.end(); it++){
if (tid && it->first != tid){continue;} if (tid != INVALID_TRACK_ID && it->first != tid){continue;}
if (mId == 0){mId = it->first;} size_t mId = (mappingId == INVALID_TRACK_ID ? it->first : mappingId);
if (meta.tracks.count(mId) && meta.tracks[mId].codec.size()){continue;} size_t idx = meta.trackIDToIndex(mId, getpid());
if (idx != INVALID_TRACK_ID && meta.getCodec(idx).size()){continue;}
// We now know we have to add a new track, OR the current track still needs it metadata set
bool addNewTrack = false;
std::string type, codec, init;
uint64_t width = 0, height = 0, fpks = 0, size = 0, rate = 0, channels = 0;
switch (it->second){ switch (it->second){
case H264:{ case H264:{
@ -840,15 +845,11 @@ namespace TS{
MEDIUM_MSG("Aborted meta fill for h264 track %lu: no SPS/PPS", it->first); MEDIUM_MSG("Aborted meta fill for h264 track %lu: no SPS/PPS", it->first);
continue; continue;
} }
meta.tracks[mId].type = "video"; // First generate needed data
meta.tracks[mId].codec = "H264";
meta.tracks[mId].trackID = mId;
std::string tmpBuffer = spsInfo[it->first]; std::string tmpBuffer = spsInfo[it->first];
h264::sequenceParameterSet sps(spsInfo[it->first].data(), spsInfo[it->first].size()); h264::sequenceParameterSet sps(tmpBuffer.data(), tmpBuffer.size());
h264::SPSMeta spsChar = sps.getCharacteristics(); h264::SPSMeta spsChar = sps.getCharacteristics();
meta.tracks[mId].width = spsChar.width;
meta.tracks[mId].height = spsChar.height;
meta.tracks[mId].fpks = spsChar.fps * 1000;
MP4::AVCC avccBox; MP4::AVCC avccBox;
avccBox.setVersion(1); avccBox.setVersion(1);
avccBox.setProfile(spsInfo[it->first][1]); avccBox.setProfile(spsInfo[it->first][1]);
@ -858,103 +859,111 @@ namespace TS{
avccBox.setSPS(spsInfo[it->first]); avccBox.setSPS(spsInfo[it->first]);
avccBox.setPPSCount(1); avccBox.setPPSCount(1);
avccBox.setPPS(ppsInfo[it->first]); avccBox.setPPS(ppsInfo[it->first]);
meta.tracks[mId].init = std::string(avccBox.payload(), avccBox.payloadSize());
// Then set all data for track
addNewTrack = true;
type = "video";
codec = "H264";
width = spsChar.width;
height = spsChar.height;
fpks = spsChar.fps * 1000;
init.assign(avccBox.payload(), avccBox.payloadSize());
}break; }break;
case H265:{ case H265:{
if (!hevcInfo.count(it->first) || !hevcInfo[it->first].haveRequired()){ if (!hevcInfo.count(it->first) || !hevcInfo[it->first].haveRequired()){
MEDIUM_MSG("Aborted meta fill for hevc track %lu: no info nal unit", it->first); MEDIUM_MSG("Aborted meta fill for hevc track %lu: no info nal unit", it->first);
continue; continue;
} }
meta.tracks[mId].type = "video"; addNewTrack = true;
meta.tracks[mId].codec = "HEVC"; type = "video";
meta.tracks[mId].trackID = mId; codec = "HEVC";
meta.tracks[mId].init = hevcInfo[it->first].generateHVCC(); init = hevcInfo[it->first].generateHVCC();
h265::metaInfo metaInfo = hevcInfo[it->first].getMeta(); h265::metaInfo metaInfo = hevcInfo[it->first].getMeta();
meta.tracks[mId].width = metaInfo.width; width = metaInfo.width;
meta.tracks[mId].height = metaInfo.height; height = metaInfo.height;
meta.tracks[mId].fpks = metaInfo.fps * 1000; fpks = metaInfo.fps * 1000;
int pmtCount = associationTable.getProgramCount();
for (int i = 0; i < pmtCount; i++){
int pid = associationTable.getProgramPID(i);
ProgramMappingEntry entry = mappingTable[pid].getEntry(0);
while (entry){
if (entry.getElementaryPid() == tid){
meta.tracks[mId].lang =
ProgramDescriptors(entry.getESInfo(), entry.getESInfoLength()).getLanguage();
}
entry.advance();
}
}
}break; }break;
case MPEG2:{ case MPEG2:{
meta.tracks[mId].type = "video"; addNewTrack = true;
meta.tracks[mId].codec = "MPEG2"; type = "video";
meta.tracks[mId].trackID = mId; codec = "MPEG2";
meta.tracks[mId].init = std::string("\000\000\001", 3) + mpeg2SeqHdr[it->first] + init = std::string("\000\000\001", 3) + mpeg2SeqHdr[it->first] +
std::string("\000\000\001", 3) + mpeg2SeqExt[it->first]; std::string("\000\000\001", 3) + mpeg2SeqExt[it->first];
Mpeg::MPEG2Info info = Mpeg::parseMPEG2Header(init);
Mpeg::MPEG2Info info = Mpeg::parseMPEG2Header(meta.tracks[mId].init); width = info.width;
meta.tracks[mId].width = info.width; height = info.height;
meta.tracks[mId].height = info.height; fpks = info.fps * 1000;
meta.tracks[mId].fpks = info.fps * 1000;
}break; }break;
case ID3:{ case ID3:{
meta.tracks[mId].type = "meta"; addNewTrack = true;
meta.tracks[mId].codec = "ID3"; type = "meta";
meta.tracks[mId].trackID = mId; codec = "ID3";
meta.tracks[mId].init = metaInit[it->first]; init = metaInit[it->first];
}break;
case META:{
addNewTrack = true;
type = "meta";
codec = "RAW";
init = metaInit[it->first];
}break; }break;
case AC3:{ case AC3:{
meta.tracks[mId].type = "audio"; addNewTrack = true;
meta.tracks[mId].codec = "AC3"; type = "audio";
meta.tracks[mId].trackID = mId; codec = "AC3";
meta.tracks[mId].size = 16; size = 16;
///\todo Fix these 2 values
meta.tracks[mId].rate = 0;
meta.tracks[mId].channels = 0;
}break; }break;
case MP2:{ case MP2:{
meta.tracks[mId].type = "audio"; addNewTrack = true;
meta.tracks[mId].codec = "MP2";
meta.tracks[mId].trackID = mId;
Mpeg::MP2Info info = Mpeg::parseMP2Header(mp2Hdr[it->first]); Mpeg::MP2Info info = Mpeg::parseMP2Header(mp2Hdr[it->first]);
meta.tracks[mId].rate = info.sampleRate; type = "audio";
meta.tracks[mId].channels = info.channels; codec = (info.layer == 3 ? "MP3" : "MP2");
rate = info.sampleRate;
///\todo Fix this value channels = info.channels;
meta.tracks[mId].size = 0;
}break; }break;
case AAC:{ case AAC:{
meta.tracks[mId].type = "audio"; addNewTrack = true;
meta.tracks[mId].codec = "AAC"; init.resize(2);
meta.tracks[mId].trackID = mId; init[0] = ((adtsInfo[it->first].getAACProfile() & 0x1F) << 3) |
meta.tracks[mId].size = 16; ((adtsInfo[it->first].getFrequencyIndex() & 0x0E) >> 1);
meta.tracks[mId].rate = adtsInfo[it->first].getFrequency(); init[1] = ((adtsInfo[it->first].getFrequencyIndex() & 0x01) << 7) |
meta.tracks[mId].channels = adtsInfo[it->first].getChannelCount(); ((adtsInfo[it->first].getChannelConfig() & 0x0F) << 3);
char audioInit[2]; // 5 bits object type, 4 bits frequency index, 4 bits channel index
audioInit[0] = ((adtsInfo[it->first].getAACProfile() & 0x1F) << 3) | type = "audio";
((adtsInfo[it->first].getFrequencyIndex() & 0x0E) >> 1); codec = "AAC";
audioInit[1] = ((adtsInfo[it->first].getFrequencyIndex() & 0x01) << 7) | size = 16;
((adtsInfo[it->first].getChannelConfig() & 0x0F) << 3); rate = adtsInfo[it->first].getFrequency();
meta.tracks[mId].init = std::string(audioInit, 2); channels = adtsInfo[it->first].getChannelCount();
}break; }break;
} }
// Add track to meta here, if newTrack is set. Otherwise only re-initialize values
if (idx == INVALID_TRACK_ID){
if (!addNewTrack){return;}
idx = meta.addTrack();
}
meta.setType(idx, type);
meta.setCodec(idx, codec);
meta.setID(idx, mId);
if (init.size()){meta.setInit(idx, init);}
meta.setWidth(idx, width);
meta.setHeight(idx, height);
meta.setFpks(idx, fpks);
meta.setSize(idx, size);
meta.setRate(idx, rate);
meta.setChannels(idx, channels);
size_t pmtCount = associationTable.getProgramCount(); size_t pmtCount = associationTable.getProgramCount();
for (size_t i = 0; i < pmtCount; i++){ for (size_t i = 0; i < pmtCount; i++){
uint32_t pid = associationTable.getProgramPID(i); uint32_t pid = associationTable.getProgramPID(i);
ProgramMappingEntry entry = mappingTable[pid].getEntry(0); ProgramMappingEntry entry = mappingTable[pid].getEntry(0);
while (entry){ while (entry){
if (entry.getElementaryPid() == tid){ if (entry.getElementaryPid() == tid){
meta.tracks[mId].lang = meta.setLang(idx, ProgramDescriptors(entry.getESInfo(), entry.getESInfoLength()).getLanguage());
ProgramDescriptors(entry.getESInfo(), entry.getESInfoLength()).getLanguage();
} }
entry.advance(); entry.advance();
} }
} }
MEDIUM_MSG("Initialized track %lu as %s %s", it->first, meta.tracks[mId].codec.c_str(), MEDIUM_MSG("Initialized track %lu as %s %s", idx, codec.c_str(), type.c_str());
meta.tracks[mId].type.c_str());
} }
} }
@ -983,7 +992,8 @@ namespace TS{
case AC3: case AC3:
case ID3: case ID3:
case MP2: case MP2:
case MPEG2: result.insert(entry.getElementaryPid()); break; case MPEG2:
case META: result.insert(entry.getElementaryPid()); break;
default: break; default: break;
} }
entry.advance(); entry.advance();

View file

@ -18,7 +18,8 @@ namespace TS{
H265 = 0x24, H265 = 0x24,
ID3 = 0x15, ID3 = 0x15,
MPEG2 = 0x02, MPEG2 = 0x02,
MP2 = 0x03 MP2 = 0x03,
META = 0x06
}; };
class ADTSRemainder{ class ADTSRemainder{
@ -57,10 +58,10 @@ namespace TS{
bool hasPacketOnEachTrack() const; bool hasPacketOnEachTrack() const;
bool hasPacket(size_t tid) const; bool hasPacket(size_t tid) const;
bool hasPacket() const; bool hasPacket() const;
void getPacket(size_t tid, DTSC::Packet &pack); void getPacket(size_t tid, DTSC::Packet &pack, size_t mappedAs = INVALID_TRACK_ID);
uint32_t getEarliestPID(); uint32_t getEarliestPID();
void getEarliestPacket(DTSC::Packet &pack); void getEarliestPacket(DTSC::Packet &pack);
void initializeMetadata(DTSC::Meta &meta, size_t tid = 0, size_t mappingId = 0); void initializeMetadata(DTSC::Meta &meta, size_t tid = INVALID_TRACK_ID, size_t mappingId = INVALID_TRACK_ID);
void partialClear(); void partialClear();
void clear(); void clear();
void finish(); void finish();

View file

@ -396,6 +396,7 @@ namespace Util{
void FieldAccX::set(const std::string &val, size_t recordNo){ void FieldAccX::set(const std::string &val, size_t recordNo){
char *place = src->getPointer(field, recordNo); char *place = src->getPointer(field, recordNo);
memcpy(place, val.data(), std::min((size_t)field.size, val.size())); memcpy(place, val.data(), std::min((size_t)field.size, val.size()));
place[std::min((size_t)field.size - 1, val.size())] = 0;
} }
/// If waitReady is true (default), waits for isReady() to return true in 50ms sleep increments. /// If waitReady is true (default), waits for isReady() to return true in 50ms sleep increments.
@ -939,10 +940,12 @@ namespace Util{
} }
FieldAccX RelAccX::getFieldAccX(const std::string &fName){ FieldAccX RelAccX::getFieldAccX(const std::string &fName){
if (!fields.count(fName)){return FieldAccX();}
return FieldAccX(this, fields.at(fName)); return FieldAccX(this, fields.at(fName));
} }
RelAccXFieldData RelAccX::getFieldData(const std::string &fName) const{ RelAccXFieldData RelAccX::getFieldData(const std::string &fName) const{
if (!fields.count(fName)){return RelAccXFieldData();}
return fields.at(fName); return fields.at(fName);
} }
}// namespace Util }// namespace Util

View file

@ -63,7 +63,11 @@ namespace Util{
uint8_t type; uint8_t type;
uint32_t size; uint32_t size;
uint32_t offset; uint32_t offset;
RelAccXFieldData(){} RelAccXFieldData(){
type = 0;
size = 0;
offset = 0;
}
RelAccXFieldData(uint8_t t, uint32_t s, uint32_t o){ RelAccXFieldData(uint8_t t, uint32_t s, uint32_t o){
type = t; type = t;
size = s; size = s;

View file

@ -67,10 +67,10 @@ int Analyser::run(Util::Config &conf){
if (validate && ((finTime - upTime + 10) * 1000 < mediaTime)){ if (validate && ((finTime - upTime + 10) * 1000 < mediaTime)){
uint32_t sleepMs = mediaTime - (Util::bootSecs() - upTime + 10) * 1000; uint32_t sleepMs = mediaTime - (Util::bootSecs() - upTime + 10) * 1000;
if ((finTime - upTime + sleepMs / 1000) >= timeOut){ if ((finTime - upTime + sleepMs / 1000) >= timeOut){
WARN_MSG("Reached timeout of %llu seconds, stopping", timeOut); WARN_MSG("Reached timeout of %" PRIu64 " seconds, stopping", timeOut);
return 3; return 3;
} }
INFO_MSG("Sleeping for %lums", sleepMs); INFO_MSG("Sleeping for %" PRIu32 "ms", sleepMs);
Util::sleep(sleepMs); Util::sleep(sleepMs);
finTime = Util::bootSecs(); finTime = Util::bootSecs();
} }
@ -80,7 +80,7 @@ int Analyser::run(Util::Config &conf){
return 4; return 4;
} }
if ((finTime - upTime) >= timeOut){ if ((finTime - upTime) >= timeOut){
WARN_MSG("Reached timeout of %llu seconds, stopping", timeOut); WARN_MSG("Reached timeout of %" PRIu64 " seconds, stopping", timeOut);
return 3; return 3;
} }
} }

View file

@ -30,17 +30,14 @@ bool getDelimBlock(std::string &data, std::string name, size_t &blockStart, size
offset--; offset--;
blockStart = data.find(delim, offset); blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
offset = blockStart + 1; // skip single character! offset = blockStart + 1; // skip single character!
blockEnd = data.find(delim, offset); blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos){ if (blockStart == std::string::npos || blockEnd == std::string::npos){
return false; // no start/end quotes found return false; // no start/end quotes found
} }
blockEnd++; // include delim blockEnd++; // include delim
// DEBUG_MSG(DLVL_INFO, "getDelimPos: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true; return true;
} }
@ -50,15 +47,12 @@ bool getValueBlock(std::string &data, std::string name, size_t &blockStart, size
return false; // name string not found. return false; // name string not found.
} }
blockStart = data.find(delim, offset); blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
blockStart++; // clip off quote characters blockStart++; // clip off quote characters
offset = blockStart; // skip single character! offset = blockStart; // skip single character!
blockEnd = data.find(delim, offset); blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos){ if (blockStart == std::string::npos || blockEnd == std::string::npos){
return false; // no start/end quotes found return false; // no start/end quotes found
} }
// DEBUG_MSG(DLVL_INFO, "getValueBlock: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true; return true;
} }
@ -67,23 +61,17 @@ bool getString(std::string &data, std::string name, std::string &output){
size_t blockEnd = 0; size_t blockEnd = 0;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){ if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block. return false; // could not find value in this data block.
} }
// DEBUG_MSG(DLVL_INFO, "data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) )
output = data.substr(blockStart, (blockEnd - blockStart)); output = data.substr(blockStart, (blockEnd - blockStart));
// looks like this function is working as expected
// DEBUG_MSG(DLVL_INFO, "data in getstring %s", (data.substr(blockStart,(blockEnd-blockStart))).c_str());
return true; return true;
} }
bool getLong(std::string &data, std::string name, long &output){ bool getLong(std::string &data, std::string name, long &output){
size_t blockStart, blockEnd; size_t blockStart, blockEnd;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){ if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block. return false; // could not find value in this data block.
} }
// DEBUG_MSG(DLVL_INFO, "name: %s data in atol %s",name.c_str(), (data.substr(blockStart,(blockEnd-blockStart))).c_str());
output = atol((data.substr(blockStart, (blockEnd - blockStart))).c_str()); output = atol((data.substr(blockStart, (blockEnd - blockStart))).c_str());
return true; return true;
} }
@ -96,7 +84,7 @@ bool getBlock(std::string &data, std::string name, int offset, size_t &blockStar
} }
if (blockStart == std::string::npos){ if (blockStart == std::string::npos){
DEBUG_MSG(DLVL_INFO, "no block start found for name: %s at offset: %i", name.c_str(), offset); INFO_MSG("no block start found for name: %s at offset: %i", name.c_str(), offset);
return false; return false;
} }
@ -104,75 +92,71 @@ bool getBlock(std::string &data, std::string name, int offset, size_t &blockStar
if (blockEnd == std::string::npos){ if (blockEnd == std::string::npos){
blockEnd = data.find("/>", blockStart); blockEnd = data.find("/>", blockStart);
if (blockEnd == std::string::npos){ if (blockEnd == std::string::npos){
DEBUG_MSG(DLVL_INFO, "no block end found."); INFO_MSG("no block end found.");
return false; return false;
} }
size_t temp = data.find("<", blockStart + 1, (blockEnd - blockStart - 1)); // the +1 is to avoid re-interpreting the starting < //TODO!! size_t temp = data.find("<", blockStart + 1,
(blockEnd - blockStart - 1)); // the +1 is to avoid re-interpreting the starting < //TODO!!
if (temp != std::string::npos){// all info is epxected between <name ... /> if (temp != std::string::npos){// all info is epxected between <name ... />
DEBUG_MSG(DLVL_FAIL, "block start found before block end. offset: %lu block: %s", temp, data.c_str()); FAIL_MSG("block start found before block end. offset: %lu block: %s", temp, data.c_str());
return false; return false;
} }
// DEBUG_MSG(DLVL_FAIL, "special block end found");
blockEnd += 2; // position after /> blockEnd += 2; // position after />
}else{ }else{
blockEnd += name.size() + 2; // position after /name> blockEnd += name.size() + 2; // position after /name>
} }
// DEBUG_MSG(DLVL_INFO, "getBlock: start: %i end: %i",blockStart,blockEnd);
return true; return true;
} }
bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// DEBUG_MSG(DLVL_INFO, "Parsing adaptationSet: %s", data.c_str());
size_t offset = 0; size_t offset = 0;
size_t blockStart, blockEnd; size_t blockStart, blockEnd;
tempSD.trackType = OTHER; tempSD.trackType = OTHER;
// get value: mimetype //todo: handle this! // get value: mimetype //todo: handle this!
std::string mimeType; std::string mimeType;
if (!getString( if (!getString(data, "mimeType",
data, "mimeType", mimeType)){// get first occurence of mimeType. --> this will break if multiple mimetypes
mimeType)){// get first occurence of mimeType. --> this will break if multiple mimetypes // should be read from this block because no offset is provided. solution:
// should be read from this block because no offset is provided. solution: // use this on a substring containing the desired information.
// use this on a substring containing the desired information. FAIL_MSG("mimeType not found");
DEBUG_MSG(DLVL_FAIL, "mimeType not found");
return false; return false;
} }
DEBUG_MSG(DLVL_INFO, "mimeType: %s", mimeType.c_str()); // checked, OK INFO_MSG("mimeType: %s", mimeType.c_str()); // checked, OK
if (mimeType.find("video") != std::string::npos){tempSD.trackType = VIDEO;} if (mimeType.find("video") != std::string::npos){tempSD.trackType = VIDEO;}
if (mimeType.find("audio") != std::string::npos){tempSD.trackType = AUDIO;} if (mimeType.find("audio") != std::string::npos){tempSD.trackType = AUDIO;}
if (tempSD.trackType == OTHER){ if (tempSD.trackType == OTHER){
DEBUG_MSG(DLVL_FAIL, "no audio or video type found. giving up."); FAIL_MSG("no audio or video type found. giving up.");
return false; return false;
} }
// find an ID within this adaptationSet block. // find an ID within this adaptationSet block.
if (!getBlock(data, (std::string) "Representation", offset, blockStart, blockEnd)){ if (!getBlock(data, (std::string) "Representation", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "Representation not found"); FAIL_MSG("Representation not found");
return false; return false;
} }
// representation string // representation string
std::string block = data.substr(blockStart, (blockEnd - blockStart)); std::string block = data.substr(blockStart, (blockEnd - blockStart));
DEBUG_MSG(DLVL_INFO, "Representation block: %s", block.c_str()); INFO_MSG("Representation block: %s", block.c_str());
// check if block is not junk? ///\todo check if block is not junk?
if (!getLong(block, "id", tempSD.trackID)){ if (!getLong(block, "id", tempSD.trackID)){
DEBUG_MSG(DLVL_FAIL, "Representation id not found in block %s", block.c_str()); FAIL_MSG("Representation id not found in block %s", block.c_str());
return false; return false;
} }
DEBUG_MSG(DLVL_INFO, "Representation/id: %li", tempSD.trackID); // checked, OK INFO_MSG("Representation/id: %li", tempSD.trackID); // checked, OK
offset = 0; offset = 0;
// get values from SegmentTemplate // get values from SegmentTemplate
if (!getBlock(data, (std::string) "SegmentTemplate", offset, blockStart, blockEnd)){ if (!getBlock(data, (std::string) "SegmentTemplate", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTemplate not found"); FAIL_MSG("SegmentTemplate not found");
return false; return false;
} }
block = data.substr(blockStart, (blockEnd - blockStart)); block = data.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "SegmentTemplate block: %s",block.c_str()); //OK
getLong(block, "timescale", tempSD.timeScale); getLong(block, "timescale", tempSD.timeScale);
getString(block, "media", tempSD.media); getString(block, "media", tempSD.media);
@ -181,20 +165,19 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
size_t tmpBlockStart = 0; size_t tmpBlockStart = 0;
size_t tmpBlockEnd = 0; size_t tmpBlockEnd = 0;
if (!getDelimBlock(tempSD.media, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){ if (!getDelimBlock(tempSD.media, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s", tempSD.media.c_str()); FAIL_MSG("Failed to find and replace $RepresentationID$ in %s", tempSD.media.c_str());
return false; return false;
} }
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d"); tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.media, "Time", tmpBlockStart, tmpBlockEnd, "$")){ if (!getDelimBlock(tempSD.media, "Time", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $Time$ in %s", tempSD.media.c_str()); FAIL_MSG("Failed to find and replace $Time$ in %s", tempSD.media.c_str());
return false; return false;
} }
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d"); tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.initialization, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){ if (!getDelimBlock(tempSD.initialization, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s", FAIL_MSG("Failed to find and replace $RepresentationID$ in %s", tempSD.initialization.c_str());
tempSD.initialization.c_str());
return false; return false;
} }
tempSD.initialization.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d"); tempSD.initialization.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
@ -202,12 +185,12 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// get segment timeline block from within segment template: // get segment timeline block from within segment template:
size_t blockOffset = 0; // offset should be 0 because this is a new block size_t blockOffset = 0; // offset should be 0 because this is a new block
if (!getBlock(block, "SegmentTimeline", blockOffset, blockStart, blockEnd)){ if (!getBlock(block, "SegmentTimeline", blockOffset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTimeline block not found"); FAIL_MSG("SegmentTimeline block not found");
return false; return false;
} }
std::string block2 = block.substr(blockStart, (blockEnd - blockStart)); // overwrites previous block (takes just the segmentTimeline part std::string block2 = block.substr(blockStart,
// DEBUG_MSG(DLVL_INFO, "SegmentTimeline block: %s",block2.c_str()); //OK (blockEnd - blockStart)); // overwrites previous block (takes just the segmentTimeline part
int numS = 0; int numS = 0;
offset = 0; offset = 0;
@ -216,10 +199,10 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
while (1){ while (1){
if (!getBlock(block2, "S", offset, blockStart, blockEnd)){ if (!getBlock(block2, "S", offset, blockStart, blockEnd)){
if (numS == 0){ if (numS == 0){
DEBUG_MSG(DLVL_FAIL, "no S found within SegmentTimeline"); FAIL_MSG("no S found within SegmentTimeline");
return false; return false;
}else{ }else{
DEBUG_MSG(DLVL_INFO, "all S found within SegmentTimeline %i", numS); INFO_MSG("all S found within SegmentTimeline %i", numS);
return true; // break; //escape from while loop (to return true) return true; // break; //escape from while loop (to return true)
} }
} }
@ -227,16 +210,14 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// stuff S data into: currentPos // stuff S data into: currentPos
// searching for t(start position) // searching for t(start position)
std::string sBlock = block2.substr(blockStart, (blockEnd - blockStart)); std::string sBlock = block2.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "S found. offset: %i blockStart: %i blockend: %i block: %s",offset,blockStart, blockEnd, sBlock.c_str()); //OK!
if (getLong(sBlock, "t", timeValue)){ if (getLong(sBlock, "t", timeValue)){
totalDuration = timeValue; // reset totalDuration to value of t totalDuration = timeValue; // reset totalDuration to value of t
} }
if (!getLong(sBlock, "d", timeValue)){// expected duration in every S. if (!getLong(sBlock, "d", timeValue)){// expected duration in every S.
DEBUG_MSG(DLVL_FAIL, "no d found within S"); FAIL_MSG("no d found within S");
return false; return false;
} }
// stuff data with old value (start of block) // stuff data with old value (start of block)
// DEBUG_MSG(DLVL_INFO, "stuffing info from S into set");
seekPos thisPos; seekPos thisPos;
thisPos.trackType = tempSD.trackType; thisPos.trackType = tempSD.trackType;
thisPos.trackID = tempSD.trackID; thisPos.trackID = tempSD.trackID;
@ -249,7 +230,6 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
static char charBuf[512]; static char charBuf[512];
snprintf(charBuf, 512, tempSD.media.c_str(), tempSD.trackID, totalDuration); snprintf(charBuf, 512, tempSD.media.c_str(), tempSD.trackID, totalDuration);
thisPos.url.assign(charBuf); thisPos.url.assign(charBuf);
// DEBUG_MSG(DLVL_INFO, "media url (from rep.ID %d, startTime %d): %s", tempSD.trackID, totalDuration,thisPos.url.c_str());
currentPos.insert(thisPos); // assumes insert copies all data in seekPos struct. currentPos.insert(thisPos); // assumes insert copies all data in seekPos struct.
totalDuration += timeValue; // update totalDuration totalDuration += timeValue; // update totalDuration
@ -265,30 +245,30 @@ bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<Stre
size_t currentOffset = 0; size_t currentOffset = 0;
size_t adaptationSetStart; size_t adaptationSetStart;
size_t adaptationSetEnd; size_t adaptationSetEnd;
// DEBUG_MSG(DLVL_INFO, "body received: %s", body.c_str());
while (getBlock(body, "AdaptationSet", currentOffset, adaptationSetStart, adaptationSetEnd)){ while (getBlock(body, "AdaptationSet", currentOffset, adaptationSetStart, adaptationSetEnd)){
tempSD.adaptationSet = numAdaptationSet; tempSD.adaptationSet = numAdaptationSet;
numAdaptationSet++; numAdaptationSet++;
DEBUG_MSG(DLVL_INFO, "adaptationSet found. start: %lu end: %lu num: %lu ", adaptationSetStart, INFO_MSG("adaptationSet found. start: %lu end: %lu num: %lu ", adaptationSetStart,
adaptationSetEnd, (adaptationSetEnd - adaptationSetStart)); adaptationSetEnd, (adaptationSetEnd - adaptationSetStart));
// get substring: from <adaptationSet... to /adaptationSet> // get substring: from <adaptationSet... to /adaptationSet>
std::string adaptationSet = body.substr(adaptationSetStart, (adaptationSetEnd - adaptationSetStart)); std::string adaptationSet = body.substr(adaptationSetStart, (adaptationSetEnd - adaptationSetStart));
// function was verified: output as expected. // function was verified: output as expected.
if (!parseAdaptationSet(adaptationSet, currentPos)){ if (!parseAdaptationSet(adaptationSet, currentPos)){
DEBUG_MSG(DLVL_FAIL, "parseAdaptationSet returned false."); // this also happens in the case of OTHER mimetype. in that case it might be FAIL_MSG("parseAdaptationSet returned false."); // this also happens in the case of OTHER mimetype.
// desirable to continue searching for valid data instead of quitting. // in that case it might be desirable to continue
// searching for valid data instead of quitting.
return false; return false;
} }
streamData.push_back(tempSD); // put temp values into adaptation set vector streamData.push_back(tempSD); // put temp values into adaptation set vector
currentOffset = adaptationSetEnd; // the getblock function should make sure End is at the correct offset. currentOffset = adaptationSetEnd; // the getblock function should make sure End is at the correct offset.
} }
if (numAdaptationSet == 0){ if (numAdaptationSet == 0){
DEBUG_MSG(DLVL_FAIL, "no adaptationSet found."); FAIL_MSG("no adaptationSet found.");
return false; return false;
} }
DEBUG_MSG(DLVL_INFO, "all adaptation sets found. total: %i", numAdaptationSet); INFO_MSG("all adaptation sets found. total: %i", numAdaptationSet);
return true; return true;
} }
@ -297,7 +277,7 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){
url = conf.getString("url"); url = conf.getString("url");
if (url.substr(0, 7) != "http://"){ if (url.substr(0, 7) != "http://"){
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://"); FAIL_MSG("The URL must start with http://");
// return -1; // return -1;
exit(1); exit(1);
} }
@ -329,9 +309,9 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){
} }
// url: // url:
DEBUG_MSG(DLVL_INFO, "url %s server: %s port: %d", url.c_str(), server.c_str(), port); INFO_MSG("url %s server: %s port: %d", url.c_str(), server.c_str(), port);
urlPrependStuff = url.substr(0, url.rfind("/") + 1); urlPrependStuff = url.substr(0, url.rfind("/") + 1);
DEBUG_MSG(DLVL_INFO, "prepend stuff: %s", urlPrependStuff.c_str()); INFO_MSG("prepend stuff: %s", urlPrependStuff.c_str());
if (!conn){conn.open(server, port, false);} if (!conn){conn.open(server, port, false);}
pos = 0; pos = 0;
@ -346,13 +326,8 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){
currentPos; currentPos;
streamData; streamData;
// DEBUG_MSG(DLVL_INFO, "body received: %s", H.body.c_str()); //keeps giving empty stuff :(
// DEBUG_MSG(DLVL_INFO, "url %s ", url.c_str());
// std::ifstream in(url.c_str());
// std::string s((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (!parseXML(H.body, currentPos, streamData)){ if (!parseXML(H.body, currentPos, streamData)){
DEBUG_MSG(DLVL_FAIL, "Manifest parsing failed. body: \n %s", H.body.c_str()); FAIL_MSG("Manifest parsing failed. body: \n %s", H.body.c_str());
if (conf.getString("mode") == "validate"){ if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs(); long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl; std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
@ -362,30 +337,30 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){
} }
H.Clean(); H.Clean();
DEBUG_MSG(DLVL_INFO, "*********"); INFO_MSG("*********");
DEBUG_MSG(DLVL_INFO, "*SUMMARY*"); INFO_MSG("*SUMMARY*");
DEBUG_MSG(DLVL_INFO, "*********"); INFO_MSG("*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size()); INFO_MSG("num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++){ for (unsigned int i = 0; i < streamData.size(); i++){
DEBUG_MSG(DLVL_INFO, ""); INFO_MSG("");
DEBUG_MSG(DLVL_INFO, "ID in vector %d", i); INFO_MSG("ID in vector %d", i);
DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID); INFO_MSG("trackID %ld", streamData[i].trackID);
DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet); INFO_MSG("adaptationSet %d", streamData[i].adaptationSet);
DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType); INFO_MSG("trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale); INFO_MSG("TimeScale %ld", streamData[i].timeScale);
DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str()); INFO_MSG("Media string %s", streamData[i].media.c_str());
DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str()); INFO_MSG("Init string %s", streamData[i].initialization.c_str());
} }
DEBUG_MSG(DLVL_INFO, ""); INFO_MSG("");
for (unsigned int i = 0; i < streamData.size(); i++){// get init url for (unsigned int i = 0; i < streamData.size(); i++){// get init url
static char charBuf[512]; static char charBuf[512];
snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID); snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID);
streamData[i].initURL.assign(charBuf); streamData[i].initURL.assign(charBuf);
DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ", INFO_MSG("init url for adaptationSet %d trackID %ld: %s ", streamData[i].adaptationSet,
streamData[i].adaptationSet, streamData[i].trackID, streamData[i].initURL.c_str()); streamData[i].trackID, streamData[i].initURL.c_str());
} }
} }
@ -403,8 +378,7 @@ dashAnalyser::~dashAnalyser(){
int dashAnalyser::doAnalyse(){ int dashAnalyser::doAnalyse(){
// DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str()); ///\todo match adaptation set and track id?
// match adaptation set and track id?
int tempID = 0; int tempID = 0;
for (unsigned int i = 0; i < streamData.size(); i++){ for (unsigned int i = 0; i < streamData.size(); i++){
if (streamData[i].trackID == currentPos.begin()->trackID && if (streamData[i].trackID == currentPos.begin()->trackID &&
@ -415,20 +389,16 @@ int dashAnalyser::doAnalyse(){
HTTP::Parser H; HTTP::Parser H;
H.url = urlPrependStuff; H.url = urlPrependStuff;
H.url.append(currentPos.begin()->url); H.url.append(currentPos.begin()->url);
DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(), INFO_MSG("Retrieving segment: %s (%llu-%llu)", H.url.c_str(), currentPos.begin()->seekTime,
currentPos.begin()->seekTime, currentPos.begin()->seekTime + currentPos.begin()->duration); currentPos.begin()->seekTime + currentPos.begin()->duration);
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut? H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut?
H.SendRequest(conn); H.SendRequest(conn);
// TODO: get response? // TODO: get response?
H.Clean(); H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))){}// ehm... while (conn && (!conn.spool() || !H.Read(conn))){}// ehm...
// std::cout << "leh vomi: "<<H.body <<std::endl;
// DEBUG_MSG(DLVL_INFO, "zut: %s", H.body.c_str());
// strBuf[tempID].append(H.body);
if (!H.body.size()){ if (!H.body.size()){
DEBUG_MSG(DLVL_FAIL, "No data downloaded from %s", H.url.c_str()); FAIL_MSG("No data downloaded from %s", H.url.c_str());
// break;
return 0; return 0;
} }
@ -440,17 +410,14 @@ int dashAnalyser::doAnalyse(){
} }
if (!mdatSeen){ if (!mdatSeen){
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-("); FAIL_MSG("No mdat present. Sadface. :-(");
// break;
return 0; return 0;
} }
if (H.body.size()){ if (H.body.size()){
DEBUG_MSG(DLVL_FAIL, "%lu bytes left in body. Assuming horrible things...", H.body.size()); //,H.body.c_str()); FAIL_MSG("%lu bytes left in body. Assuming horrible things...",
H.body.size()); //,H.body.c_str());
std::cerr << H.body << std::endl; std::cerr << H.body << std::endl;
if (beforeParse == H.body.size()){ if (beforeParse == H.body.size()){return 0;}
// break;
return 0;
}
} }
H.Clean(); H.Clean();
@ -516,7 +483,7 @@ int main2(int argc, char **argv){
std::string url = conf.getString("url"); std::string url = conf.getString("url");
if (url.substr(0, 7) != "http://"){ if (url.substr(0, 7) != "http://"){
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://"); FAIL_MSG("The URL must start with http://");
return -1; return -1;
} }
url = url.substr(7); // found problem if url is to short!! it gives out of range when entering http://meh.meh url = url.substr(7); // found problem if url is to short!! it gives out of range when entering http://meh.meh
@ -535,9 +502,9 @@ int main2(int argc, char **argv){
Socket::Connection conn(server, port, false); Socket::Connection conn(server, port, false);
// url: // url:
DEBUG_MSG(DLVL_INFO, "url %s server: %s port: %d", url.c_str(), server.c_str(), port); INFO_MSG("url %s server: %s port: %d", url.c_str(), server.c_str(), port);
std::string urlPrependStuff = url.substr(0, url.rfind("/") + 1); std::string urlPrependStuff = url.substr(0, url.rfind("/") + 1);
DEBUG_MSG(DLVL_INFO, "prepend stuff: %s", urlPrependStuff.c_str()); INFO_MSG("prepend stuff: %s", urlPrependStuff.c_str());
if (!conn){conn.open(server, port, false);} if (!conn){conn.open(server, port, false);}
unsigned int pos = 0; unsigned int pos = 0;
HTTP::Parser H; HTTP::Parser H;
@ -551,13 +518,8 @@ int main2(int argc, char **argv){
std::set<seekPos> currentPos; std::set<seekPos> currentPos;
std::vector<StreamData> streamData; std::vector<StreamData> streamData;
// DEBUG_MSG(DLVL_INFO, "body received: %s", H.body.c_str()); //keeps giving empty stuff :(
// DEBUG_MSG(DLVL_INFO, "url %s ", url.c_str());
// std::ifstream in(url.c_str());
// std::string s((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (!parseXML(H.body, currentPos, streamData)){ if (!parseXML(H.body, currentPos, streamData)){
DEBUG_MSG(DLVL_FAIL, "Manifest parsing failed. body: \n %s", H.body.c_str()); FAIL_MSG("Manifest parsing failed. body: \n %s", H.body.c_str());
if (conf.getString("mode") == "validate"){ if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs(); long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl; std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
@ -566,37 +528,36 @@ int main2(int argc, char **argv){
} }
H.Clean(); H.Clean();
DEBUG_MSG(DLVL_INFO, "*********"); INFO_MSG("*********");
DEBUG_MSG(DLVL_INFO, "*SUMMARY*"); INFO_MSG("*SUMMARY*");
DEBUG_MSG(DLVL_INFO, "*********"); INFO_MSG("*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size()); INFO_MSG("num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++){ for (unsigned int i = 0; i < streamData.size(); i++){
DEBUG_MSG(DLVL_INFO, ""); INFO_MSG("");
DEBUG_MSG(DLVL_INFO, "ID in vector %d", i); INFO_MSG("ID in vector %d", i);
DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID); INFO_MSG("trackID %ld", streamData[i].trackID);
DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet); INFO_MSG("adaptationSet %d", streamData[i].adaptationSet);
DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType); INFO_MSG("trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale); INFO_MSG("TimeScale %ld", streamData[i].timeScale);
DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str()); INFO_MSG("Media string %s", streamData[i].media.c_str());
DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str()); INFO_MSG("Init string %s", streamData[i].initialization.c_str());
} }
DEBUG_MSG(DLVL_INFO, ""); INFO_MSG("");
for (unsigned int i = 0; i < streamData.size(); i++){// get init url for (unsigned int i = 0; i < streamData.size(); i++){// get init url
static char charBuf[512]; static char charBuf[512];
snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID); snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID);
streamData[i].initURL.assign(charBuf); streamData[i].initURL.assign(charBuf);
DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ", INFO_MSG("init url for adaptationSet %d trackID %ld: %s ", streamData[i].adaptationSet,
streamData[i].adaptationSet, streamData[i].trackID, streamData[i].initURL.c_str()); streamData[i].trackID, streamData[i].initURL.c_str());
} }
while (currentPos.size() && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)){ while (currentPos.size() && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)){
// DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str());
std::cout << "blaa" << std::endl; std::cout << "blaa" << std::endl;
// match adaptation set and track id? ///\todo match adaptation set and track id?
int tempID = 0; int tempID = 0;
for (unsigned int i = 0; i < streamData.size(); i++){ for (unsigned int i = 0; i < streamData.size(); i++){
if (streamData[i].trackID == currentPos.begin()->trackID && if (streamData[i].trackID == currentPos.begin()->trackID &&
@ -607,18 +568,15 @@ int main2(int argc, char **argv){
HTTP::Parser H; HTTP::Parser H;
H.url = urlPrependStuff; H.url = urlPrependStuff;
H.url.append(currentPos.begin()->url); H.url.append(currentPos.begin()->url);
DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(), INFO_MSG("Retrieving segment: %s (%llu-%llu)", H.url.c_str(), currentPos.begin()->seekTime,
currentPos.begin()->seekTime, currentPos.begin()->seekTime + currentPos.begin()->duration); currentPos.begin()->seekTime + currentPos.begin()->duration);
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut? H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut?
H.SendRequest(conn); H.SendRequest(conn);
// TODO: get response? // TODO: get response?
H.Clean(); H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))){}// ehm... while (conn && (!conn.spool() || !H.Read(conn))){}// ehm...
// std::cout << "leh vomi: "<<H.body <<std::endl;
// DEBUG_MSG(DLVL_INFO, "zut: %s", H.body.c_str());
// strBuf[tempID].append(H.body);
if (!H.body.size()){ if (!H.body.size()){
DEBUG_MSG(DLVL_FAIL, "No data downloaded from %s", H.url.c_str()); FAIL_MSG("No data downloaded from %s", H.url.c_str());
break; break;
} }
size_t beforeParse = H.body.size(); size_t beforeParse = H.body.size();
@ -628,11 +586,12 @@ int main2(int argc, char **argv){
if (mp4Data.isType("mdat")){mdatSeen = true;} if (mp4Data.isType("mdat")){mdatSeen = true;}
} }
if (!mdatSeen){ if (!mdatSeen){
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-("); FAIL_MSG("No mdat present. Sadface. :-(");
break; break;
} }
if (H.body.size()){ if (H.body.size()){
DEBUG_MSG(DLVL_FAIL, "%lu bytes left in body. Assuming horrible things...", H.body.size()); //,H.body.c_str()); FAIL_MSG("%lu bytes left in body. Assuming horrible things...",
H.body.size()); //,H.body.c_str());
std::cerr << H.body << std::endl; std::cerr << H.body << std::endl;
if (beforeParse == H.body.size()){break;} if (beforeParse == H.body.size()){break;}
} }

View file

@ -35,7 +35,7 @@ bool AnalyserDTSC::parsePacket(){
} }
P.reInit(conn); P.reInit(conn);
if (conn && !P){ if (conn && !P){
FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes) FAIL_MSG("Invalid DTSC packet @ byte %" PRIu64, totalBytes)
return false; return false;
} }
if (!conn && !P){ if (!conn && !P){
@ -70,8 +70,7 @@ bool AnalyserDTSC::parsePacket(){
case DTSC::DTSC_HEAD:{ case DTSC::DTSC_HEAD:{
if (detail >= 3){ if (detail >= 3){
std::cout << "DTSC header: "; std::cout << "DTSC header: ";
DTSC::Meta(P).toPrettyString( std::cout << DTSC::Meta("", P.getScan()).toPrettyString();
std::cout, 0, (detail == 3 ? 0 : (detail == 4 ? 0x01 : (detail == 5 ? 0x03 : 0x07))));
} }
if (detail == 2){std::cout << "DTSC header: " << P.getScan().toPrettyString() << std::endl;} if (detail == 2){std::cout << "DTSC header: " << P.getScan().toPrettyString() << std::endl;}
if (detail == 1){ if (detail == 1){
@ -79,30 +78,34 @@ bool AnalyserDTSC::parsePacket(){
bool hasAAC = false; bool hasAAC = false;
JSON::Value result; JSON::Value result;
std::stringstream issues; std::stringstream issues;
DTSC::Meta M(P); DTSC::Meta M("", P.getScan());
for (std::map<unsigned int, DTSC::Track>::iterator it = M.tracks.begin(); it != M.tracks.end(); it++){ std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
std::string codec = M.getCodec(*it);
JSON::Value track; JSON::Value track;
track["kbits"] = (uint64_t)((double)it->second.bps * 8 / 1024); track["kbits"] = M.getBps(*it) * 8 / 1024;
track["codec"] = it->second.codec; track["codec"] = codec;
uint32_t shrtest_key = 0xFFFFFFFFul; uint32_t shrtest_key = 0xFFFFFFFFul;
uint32_t longest_key = 0; uint32_t longest_key = 0;
uint32_t shrtest_prt = 0xFFFFFFFFul; uint32_t shrtest_prt = 0xFFFFFFFFul;
uint32_t longest_prt = 0; uint32_t longest_prt = 0;
uint32_t shrtest_cnt = 0xFFFFFFFFul; uint32_t shrtest_cnt = 0xFFFFFFFFul;
uint32_t longest_cnt = 0; uint32_t longest_cnt = 0;
for (std::deque<DTSC::Key>::iterator k = it->second.keys.begin(); k != it->second.keys.end(); k++){
if (!k->getLength()){continue;} DTSC::Keys keys(M.keys(*it));
if (k->getLength() > longest_key){longest_key = k->getLength();} uint32_t firstKey = keys.getFirstValid();
if (k->getLength() < shrtest_key){shrtest_key = k->getLength();} uint32_t endKey = keys.getEndValid();
if (k->getParts() > longest_cnt){longest_cnt = k->getParts();} for (int i = firstKey; i < endKey; i++){
if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();} uint64_t keyDuration = keys.getDuration(i);
if (k->getParts()){ uint64_t keyParts = keys.getParts(i);
if ((k->getLength() / k->getParts()) > longest_prt){ if (!keyDuration){continue;}
longest_prt = (k->getLength() / k->getParts()); if (keyDuration > longest_key){longest_key = keyDuration;}
} if (keyDuration < shrtest_key){shrtest_key = keyDuration;}
if ((k->getLength() / k->getParts()) < shrtest_prt){ if (keyParts > longest_cnt){longest_cnt = keyParts;}
shrtest_prt = (k->getLength() / k->getParts()); if (keyParts < shrtest_cnt){shrtest_cnt = keyParts;}
} if (keyParts){
if ((keyDuration / keyParts) > longest_prt){longest_prt = (keyDuration / keyParts);}
if ((keyDuration / keyParts) < shrtest_prt){shrtest_prt = (keyDuration / keyParts);}
} }
} }
track["keys"]["ms_min"] = shrtest_key; track["keys"]["ms_min"] = shrtest_key;
@ -112,28 +115,28 @@ bool AnalyserDTSC::parsePacket(){
track["keys"]["frames_min"] = shrtest_cnt; track["keys"]["frames_min"] = shrtest_cnt;
track["keys"]["frames_max"] = longest_cnt; track["keys"]["frames_max"] = longest_cnt;
if (longest_prt > 500){ if (longest_prt > 500){
issues << "unstable connection (" << longest_prt << "ms " << it->second.codec << " frame)! "; issues << "unstable connection (" << longest_prt << "ms " << codec << " frame)! ";
} }
if (shrtest_cnt < 6){ if (shrtest_cnt < 6){
issues << "unstable connection (" << shrtest_cnt << " " << it->second.codec << " frames in key)! "; issues << "unstable connection (" << shrtest_cnt << " " << codec << " frames in key)! ";
} }
if (it->second.codec == "AAC"){hasAAC = true;} if (codec == "AAC"){hasAAC = true;}
if (it->second.codec == "H264"){hasH264 = true;} if (codec == "H264"){hasH264 = true;}
if (it->second.type == "video"){ if (M.getType(*it) == "video"){
track["width"] = it->second.width; track["width"] = M.getWidth(*it);
track["height"] = it->second.height; track["height"] = M.getHeight(*it);
track["fpks"] = it->second.fpks; track["fpks"] = M.getFpks(*it);
if (it->second.codec == "H264"){ if (codec == "H264"){
h264::sequenceParameterSet sps; h264::sequenceParameterSet sps;
sps.fromDTSCInit(it->second.init); sps.fromDTSCInit(M.getInit(*it));
h264::SPSMeta spsData = sps.getCharacteristics(); h264::SPSMeta spsData = sps.getCharacteristics();
track["h264"]["profile"] = spsData.profile; track["h264"]["profile"] = spsData.profile;
track["h264"]["level"] = spsData.level; track["h264"]["level"] = spsData.level;
} }
} }
result[it->second.getWritableIdentifier()] = track; result[M.getTrackIdentifier(*it)] = track;
} }
if ((hasAAC || hasH264) && M.tracks.size() > 1){ if (hasAAC || hasH264){
if (!hasAAC){issues << "HLS no audio!";} if (!hasAAC){issues << "HLS no audio!";}
if (!hasH264){issues << "HLS no video!";} if (!hasH264){issues << "HLS no video!";}
} }
@ -147,7 +150,7 @@ bool AnalyserDTSC::parsePacket(){
if (detail >= 2){std::cout << "DTCM command: " << P.getScan().toPrettyString() << std::endl;} if (detail >= 2){std::cout << "DTCM command: " << P.getScan().toPrettyString() << std::endl;}
break; break;
} }
default: FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes); break; default: FAIL_MSG("Invalid DTSC packet @ byte %" PRIu64, totalBytes); break;
} }
totalBytes += P.getDataLen(); totalBytes += P.getDataLen();

View file

@ -35,7 +35,7 @@ bool AnalyserEBML::parsePacket(){
if (dataBuffer.size() < neededBytes()){return false;} if (dataBuffer.size() < neededBytes()){return false;}
EBML::Element E(dataBuffer.data(), true); EBML::Element E(dataBuffer.data(), true);
HIGH_MSG("Read an element at position %d", prePos); HIGH_MSG("Read an element at position %zu", prePos);
if (detail >= 2){std::cout << E.toPrettyString(depthStash.size() * 2, detail);} if (detail >= 2){std::cout << E.toPrettyString(depthStash.size() * 2, detail);}
switch (E.getID()){ switch (E.getID()){
case EBML::EID_SEGMENT: case EBML::EID_SEGMENT:

View file

@ -11,7 +11,7 @@ private:
uint64_t neededBytes(); uint64_t neededBytes();
std::string dataBuffer; std::string dataBuffer;
uint64_t curPos; uint64_t curPos;
uint64_t prePos; size_t prePos;
uint64_t segmentOffset; uint64_t segmentOffset;
uint32_t lastSeekId; uint32_t lastSeekId;
uint64_t lastSeekPos; uint64_t lastSeekPos;

View file

@ -32,7 +32,8 @@ bool AnalyserFLV::parsePacket(){
// If we arrive here, we've loaded a FLV packet // If we arrive here, we've loaded a FLV packet
if (!filter || filter == flvData.data[0]){ if (!filter || filter == flvData.data[0]){
DETAIL_MED("[%llu+%llu] %s", flvData.tagTime(), flvData.offset(), flvData.tagType().c_str()); DETAIL_MED("[%" PRIu64 "+%" PRId64 "] %s", flvData.tagTime(), flvData.offset(),
flvData.tagType().c_str());
} }
mediaTime = flvData.tagTime(); mediaTime = flvData.tagTime();
return true; return true;

View file

@ -9,5 +9,5 @@ public:
private: private:
FLV::Tag flvData; FLV::Tag flvData;
long long filter; int64_t filter;
}; };

View file

@ -39,7 +39,7 @@ bool AnalyserH264::parsePacket(){
size = 0; size = 0;
nalPtr = h264::nalFactory(dataBuffer.data(), dataBuffer.size(), size, !sizePrepended); nalPtr = h264::nalFactory(dataBuffer.data(), dataBuffer.size(), size, !sizePrepended);
if (nalPtr){ if (nalPtr){
HIGH_MSG("Read a %lu-byte NAL unit at position %llu", size, prePos); HIGH_MSG("Read a %lu-byte NAL unit at position %" PRIu64, size, prePos);
if (detail >= 2){nalPtr->toPrettyString(std::cout);} if (detail >= 2){nalPtr->toPrettyString(std::cout);}
dataBuffer.erase(0, size); // erase the NAL unit we just read dataBuffer.erase(0, size); // erase the NAL unit we just read
prePos += size; prePos += size;
@ -47,7 +47,7 @@ bool AnalyserH264::parsePacket(){
///\TODO update mediaTime with current timestamp ///\TODO update mediaTime with current timestamp
}while (nalPtr); }while (nalPtr);
if (!nalPtr){ if (!nalPtr){
FAIL_MSG("Could not read a NAL unit at position %llu", prePos); FAIL_MSG("Could not read a NAL unit at position %" PRIu64, prePos);
return false; return false;
} }
return true; return true;

View file

@ -40,7 +40,7 @@ void AnalyserHLS::getParts(const std::string &body){
} }
if (!parsedPart || no > parsedPart){ if (!parsedPart || no > parsedPart){
HTTP::URL newURL = root.link(line); HTTP::URL newURL = root.link(line);
INFO_MSG("Discovered #%llu: %s", no, newURL.getUrl().c_str()); INFO_MSG("Discovered #%" PRIu64 ": %s", no, newURL.getUrl().c_str());
parts.push_back(HLSPart(newURL, no, durat)); parts.push_back(HLSPart(newURL, no, durat));
} }
++no; ++no;
@ -111,7 +111,7 @@ bool AnalyserHLS::parsePacket(){
} }
} }
if (DL.data().size() % 188){ if (DL.data().size() % 188){
FAIL_MSG("Expected a multiple of 188 bytes, received %d bytes", DL.data().size()); FAIL_MSG("Expected a multiple of 188 bytes, received %zu bytes", DL.data().size());
return false; return false;
} }
parsedPart = part.no; parsedPart = part.no;
@ -124,8 +124,8 @@ bool AnalyserHLS::parsePacket(){
// Hm. I guess we had no parts to get. // Hm. I guess we had no parts to get.
if (refreshAt && refreshAt > Util::bootSecs()){ if (refreshAt && refreshAt > Util::bootSecs()){
// We're getting a live stream. Let's wait and check again. // We're getting a live stream. Let's wait and check again.
uint32_t sleepSecs = (refreshAt - Util::bootSecs()); uint64_t sleepSecs = (refreshAt - Util::bootSecs());
INFO_MSG("Sleeping for %lu seconds", sleepSecs); INFO_MSG("Sleeping for %" PRIu64 " seconds", sleepSecs);
Util::sleep(sleepSecs * 1000); Util::sleep(sleepSecs * 1000);
} }
// The non-live case is already handled in isOpen() // The non-live case is already handled in isOpen()

View file

@ -1,4 +1,5 @@
#include "analyser_mp4.h" #include "analyser_mp4.h"
#include <mist/bitfields.h>
void AnalyserMP4::init(Util::Config &conf){ void AnalyserMP4::init(Util::Config &conf){
Analyser::init(conf); Analyser::init(conf);
@ -22,23 +23,21 @@ bool AnalyserMP4::parsePacket(){
} }
if (mp4Data.read(mp4Buffer)){ if (mp4Data.read(mp4Buffer)){
INFO_MSG("Read a box at position %d", prePos); INFO_MSG("Read a box at position %" PRIu64, prePos);
if (detail >= 2){std::cout << mp4Data.toPrettyString(0) << std::endl;} if (detail >= 2){std::cout << mp4Data.toPrettyString(0) << std::endl;}
///\TODO update mediaTime with the current timestamp ///\TODO update mediaTime with the current timestamp
return true; return true;
} }
FAIL_MSG("Could not read box at position %llu", prePos); FAIL_MSG("Could not read box at position %" PRIu64, prePos);
return false; return false;
} }
/// Calculates how many bytes we need to read a whole box. /// Calculates how many bytes we need to read a whole box.
uint64_t AnalyserMP4::neededBytes(){ uint64_t AnalyserMP4::neededBytes(){
if (mp4Buffer.size() < 4){return 4;} if (mp4Buffer.size() < 4){return 4;}
uint64_t size = ntohl(((int *)mp4Buffer.data())[0]); uint64_t size = Bit::btohl(mp4Buffer.data());
if (size != 1){return size;} if (size != 1){return size;}
if (mp4Buffer.size() < 16){return 16;} if (mp4Buffer.size() < 16){return 16;}
size = 0 + ntohl(((int *)mp4Buffer.data())[2]); size = Bit::btohll(mp4Buffer.data() + 8);
size <<= 32;
size += ntohl(((int *)mp4Buffer.data())[3]);
return size; return size;
} }

View file

@ -28,18 +28,18 @@ bool AnalyserOGG::parsePacket(){
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Opus"; sn2Codec[oggPage.getBitstreamSerialNumber()] = "Opus";
} }
if (sn2Codec[oggPage.getBitstreamSerialNumber()] != ""){ if (sn2Codec[oggPage.getBitstreamSerialNumber()] != ""){
INFO_MSG("Bitstream %llu recognized as %s", oggPage.getBitstreamSerialNumber(), INFO_MSG("Bitstream %" PRIu64 " recognized as %s", oggPage.getBitstreamSerialNumber(),
sn2Codec[oggPage.getBitstreamSerialNumber()].c_str()); sn2Codec[oggPage.getBitstreamSerialNumber()].c_str());
}else{ }else{
WARN_MSG("Bitstream %llu not recognized!", oggPage.getBitstreamSerialNumber()); WARN_MSG("Bitstream %" PRIu64 " not recognized!", oggPage.getBitstreamSerialNumber());
} }
} }
if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){ if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){
if (detail >= 2){std::cout << " Theora data" << std::endl;} if (detail >= 2){std::cout << " Theora data" << std::endl;}
static unsigned int numParts = 0; static size_t numParts = 0;
static unsigned int keyCount = 0; static size_t keyCount = 0;
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){ for (size_t i = 0; i < oggPage.getAllSegments().size(); i++){
theora::header tmpHeader((char *)oggPage.getSegment(i), oggPage.getAllSegments()[i].size()); theora::header tmpHeader((char *)oggPage.getSegment(i), oggPage.getAllSegments()[i].size());
if (tmpHeader.isHeader()){ if (tmpHeader.isHeader()){
if (tmpHeader.getHeaderType() == 0){kfgshift = tmpHeader.getKFGShift();} if (tmpHeader.getHeaderType() == 0){kfgshift = tmpHeader.getKFGShift();}

View file

@ -8,8 +8,8 @@ public:
static void init(Util::Config &conf); static void init(Util::Config &conf);
private: private:
std::map<int, std::string> sn2Codec; std::map<uint64_t, std::string> sn2Codec;
std::string oggBuffer; std::string oggBuffer;
OGG::Page oggPage; OGG::Page oggPage;
int kfgshift; uint16_t kfgshift;
}; };

View file

@ -26,7 +26,7 @@ bool AnalyserRIFF::parsePacket(){
if (dataBuffer.size() < 8){return false;} if (dataBuffer.size() < 8){return false;}
RIFF::Chunk C(dataBuffer.data(), dataBuffer.size()); RIFF::Chunk C(dataBuffer.data(), dataBuffer.size());
INFO_MSG("Read a chunk at position %d", prePos); INFO_MSG("Read a chunk at position %" PRIu64, prePos);
if (detail >= 2){C.toPrettyString(std::cout);} if (detail >= 2){C.toPrettyString(std::cout);}
///\TODO update mediaTime with the current timestamp ///\TODO update mediaTime with the current timestamp
if (C){ if (C){

View file

@ -2,6 +2,7 @@
/// Debugging tool for RTMP data. /// Debugging tool for RTMP data.
#include "analyser_rtmp.h" #include "analyser_rtmp.h"
#include <mist/bitfields.h>
void AnalyserRTMP::init(Util::Config &conf){ void AnalyserRTMP::init(Util::Config &conf){
Analyser::init(conf); Analyser::init(conf);
@ -42,23 +43,19 @@ bool AnalyserRTMP::parsePacket(){
// While we can't parse a packet, // While we can't parse a packet,
while (!next.Parse(strbuf)){ while (!next.Parse(strbuf)){
// fill our internal buffer "strbuf" in (up to) 1024 byte chunks // fill our internal buffer "strbuf" in (up to) 1024 byte chunks
if (std::cin.good()){ if (!std::cin.good()){return false;}
unsigned int charCount = 0; size_t charCount = 0;
std::string tmpbuffer; std::string tmpbuffer;
tmpbuffer.reserve(1024); tmpbuffer.reserve(1024);
while (std::cin.good() && charCount < 1024){ while (std::cin.good() && charCount < 1024){
char newchar = std::cin.get(); char newchar = std::cin.get();
if (std::cin.good()){ if (std::cin.good()){
tmpbuffer += newchar; tmpbuffer += newchar;
++read_in; ++read_in;
++charCount; ++charCount;
}
} }
strbuf.append(tmpbuffer);
}else{
// if we can't fill the buffer, and have no parsable packet(s), return false
return false;
} }
strbuf.append(tmpbuffer);
} }
// We now know for sure that we've parsed a packet // We now know for sure that we've parsed a packet
@ -72,71 +69,66 @@ bool AnalyserRTMP::parsePacket(){
break; // happens when connection breaks unexpectedly break; // happens when connection breaks unexpectedly
case 1: // set chunk size case 1: // set chunk size
RTMPStream::chunk_rec_max = ntohl(*(int *)next.data.c_str()); RTMPStream::chunk_rec_max = ntohl(*(int *)next.data.c_str());
DETAIL_MED("CTRL: Set chunk size: %i", RTMPStream::chunk_rec_max); DETAIL_MED("CTRL: Set chunk size: %" PRIu64, RTMPStream::chunk_rec_max);
break; break;
case 2: // abort message - we ignore this one case 2: // abort message - we ignore this one
DETAIL_MED("CTRL: Abort message: %i", ntohl(*(int *)next.data.c_str())); DETAIL_MED("CTRL: Abort message: %" PRIu32, Bit::btohl(next.data.data()));
// 4 bytes of stream id to drop // 4 bytes of stream id to drop
break; break;
case 3: // ack case 3: // ack
RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str()); RTMPStream::snd_window_at = Bit::btohl(next.data.data());
DETAIL_MED("CTRL: Acknowledgement: %i", RTMPStream::snd_window_at); DETAIL_MED("CTRL: Acknowledgement: %" PRIu64, RTMPStream::snd_window_at);
break; break;
case 4:{ case 4:{
short int ucmtype = ntohs(*(short int *)next.data.c_str()); int16_t ucmtype = Bit::btohs(next.data.data());
switch (ucmtype){ switch (ucmtype){
case 0: case 0:
DETAIL_MED("CTRL: User control message: stream begin %u", DETAIL_MED("CTRL: User control message: stream begin %" PRIu32, Bit::btohl(next.data.data() + 2));
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
break; break;
case 1: case 1:
DETAIL_MED("CTRL: User control message: stream EOF %u", ntohl(*(unsigned int *)(next.data.c_str() + 2))); DETAIL_MED("CTRL: User control message: stream EOF %" PRIu32, Bit::btohl(next.data.data() + 2));
break; break;
case 2: case 2:
DETAIL_MED("CTRL: User control message: stream dry %u", ntohl(*(unsigned int *)(next.data.c_str() + 2))); DETAIL_MED("CTRL: User control message: stream dry %" PRIu32, Bit::btohl(next.data.data() + 2));
break; break;
case 3: case 3:
DETAIL_MED("CTRL: User control message: setbufferlen %u", DETAIL_MED("CTRL: User control message: setbufferlen %" PRIu32, Bit::btohl(next.data.data() + 2));
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
break; break;
case 4: case 4:
DETAIL_MED("CTRL: User control message: streamisrecorded %u", DETAIL_MED("CTRL: User control message: streamisrecorded %" PRIu32, Bit::btohl(next.data.data() + 2));
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
break; break;
case 6: case 6:
DETAIL_MED("CTRL: User control message: pingrequest %u", DETAIL_MED("CTRL: User control message: pingrequest %" PRIu32, Bit::btohl(next.data.data() + 2));
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
break; break;
case 7: case 7:
DETAIL_MED("CTRL: User control message: pingresponse %u", DETAIL_MED("CTRL: User control message: pingresponse %" PRIu32, Bit::btohl(next.data.data() + 2));
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
break; break;
case 31: case 31:
case 32: case 32:
// don't know, but not interesting anyway // don't know, but not interes ting anyway
break; break;
default: default:
DETAIL_LOW("CTRL: User control message: UNKNOWN %hu - %u", ucmtype, DETAIL_LOW("CTRL: User control message: UNKNOWN %" PRId16 " - %" PRIu32, ucmtype,
ntohl(*(unsigned int *)(next.data.c_str() + 2))); Bit::btohl(next.data.data() + 2));
break; break;
} }
}break; }break;
case 5: // window size of other end case 5: // window size of other end
RTMPStream::rec_window_size = ntohl(*(int *)next.data.c_str()); RTMPStream::rec_window_size = Bit::btohl(next.data.data());
RTMPStream::rec_window_at = RTMPStream::rec_cnt; RTMPStream::rec_window_at = RTMPStream::rec_cnt;
DETAIL_MED("CTRL: Window size: %i", RTMPStream::rec_window_size); DETAIL_MED("CTRL: Window size: %" PRIu64, RTMPStream::rec_window_size);
break; break;
case 6: case 6:
RTMPStream::snd_window_size = ntohl(*(int *)next.data.c_str()); RTMPStream::snd_window_size = Bit::btohl(next.data.data());
// 4 bytes window size, 1 byte limit type (ignored) // 4 bytes window size, 1 byte limit type (ignored)
DETAIL_MED("CTRL: Set peer bandwidth: %i", RTMPStream::snd_window_size); DETAIL_MED("CTRL: Set peer bandwidth: %" PRIu64, RTMPStream::snd_window_size);
break; break;
case 8: case 8:
case 9: case 9:
if (detail >= 4 || reconstruct.good() || validate){ if (detail >= 4 || reconstruct.good() || validate){
F.ChunkLoader(next); F.ChunkLoader(next);
mediaTime = F.tagTime(); mediaTime = F.tagTime();
DETAIL_VHI("[%llu+%llu] %s", F.tagTime(), F.offset(), F.tagType().c_str()); DETAIL_VHI("[%" PRIu64 "+%" PRId64 "] %s", F.tagTime(), F.offset(), F.tagType().c_str());
if (reconstruct.good()){reconstruct.write(F.data, F.len);} if (reconstruct.good()){reconstruct.write(F.data, F.len);}
} }
break; break;

View file

@ -7,7 +7,7 @@ class AnalyserRTMP : public Analyser{
private: private:
RTMPStream::Chunk next; ///< Holds the most recently parsed RTMP chunk RTMPStream::Chunk next; ///< Holds the most recently parsed RTMP chunk
FLV::Tag F; ///< Holds the most recently created FLV packet FLV::Tag F; ///< Holds the most recently created FLV packet
unsigned int read_in; ///< Amounts of bytes read to fill 'strbuf' so far size_t read_in; ///< Amounts of bytes read to fill 'strbuf' so far
Socket::Buffer strbuf; ///< Internal buffer from where 'next' is filled Socket::Buffer strbuf; ///< Internal buffer from where 'next' is filled
AMF::Object amfdata; ///< Last read AMF object AMF::Object amfdata; ///< Last read AMF object
AMF::Object3 amf3data; ///< Last read AMF3 object AMF::Object3 amf3data; ///< Last read AMF3 object

View file

@ -14,9 +14,9 @@ void AnalyserRTSP::incoming(const DTSC::Packet &pkt){
char *dataPtr; char *dataPtr;
size_t dataSize; size_t dataSize;
pkt.getString("data", dataPtr, dataSize); pkt.getString("data", dataPtr, dataSize);
DETAIL_MED("Received %ub %sfor track %lu (%s) @ %llums", dataSize, DETAIL_MED("Received %zub %sfor track %zu (%s) @ %" PRIu64 "ms", dataSize,
pkt.getFlag("keyframe") ? "keyframe " : "", pkt.getTrackId(), pkt.getFlag("keyframe") ? "keyframe " : "", pkt.getTrackId(),
myMeta.tracks[pkt.getTrackId()].getIdentifier().c_str(), pkt.getTime()); myMeta.getTrackIdentifier(pkt.getTrackId()).c_str(), pkt.getTime());
if (detail >= 8){ if (detail >= 8){
for (uint32_t i = 0; i < dataSize; ++i){ for (uint32_t i = 0; i < dataSize; ++i){
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)dataPtr[i] << " "; std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)dataPtr[i] << " ";
@ -58,9 +58,9 @@ bool AnalyserRTSP::parsePacket(){
return true; return true;
} }
if (HTTP.hasHeader("Transport")){ if (HTTP.hasHeader("Transport")){
uint32_t trackNo = sdpState.parseSetup(HTTP, "", ""); size_t trackNo = sdpState.parseSetup(HTTP, "", "");
if (trackNo){ if (trackNo){
DETAIL_MED("Parsed transport for track: %lu", trackNo); DETAIL_MED("Parsed transport for track: %zu", trackNo);
}else{ }else{
DETAIL_MED("Could not parse transport string!"); DETAIL_MED("Could not parse transport string!");
} }
@ -95,15 +95,15 @@ bool AnalyserRTSP::parsePacket(){
RTP::Packet pkt(tcpPacket.data() + 4, len); RTP::Packet pkt(tcpPacket.data() + 4, len);
uint8_t chan = tcpHead.data()[1]; uint8_t chan = tcpHead.data()[1];
uint32_t trackNo = sdpState.getTrackNoForChannel(chan); uint32_t trackNo = sdpState.getTrackNoForChannel(chan);
DETAIL_HI("Received %ub RTP packet #%u on channel %u, time %llu", len, DETAIL_HI("Received %ub RTP packet #%u on channel %u, time %" PRIu32, len, pkt.getSequence(),
(unsigned int)pkt.getSequence(), chan, pkt.getTimeStamp()); chan, pkt.getTimeStamp());
if (!trackNo && (chan % 2) != 1){ if (!trackNo && (chan % 2) != 1){
DETAIL_MED("Received packet for unknown track number on channel %u", chan); DETAIL_MED("Received packet for unknown track number on channel %u", chan);
} }
if (trackNo){sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();} if (trackNo){sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();}
if (detail >= 10){ if (detail >= 10){
char *pl = pkt.getPayload(); const char *pl = pkt.getPayload();
uint32_t payLen = pkt.getPayloadSize(); uint32_t payLen = pkt.getPayloadSize();
for (uint32_t i = 0; i < payLen; ++i){ for (uint32_t i = 0; i < payLen; ++i){
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)pl[i] << " "; std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)pl[i] << " ";

View file

@ -46,7 +46,7 @@ bool AnalyserTS::parsePacket(){
static char packetPtr[188]; static char packetPtr[188];
std::cin.read(packetPtr, 188); std::cin.read(packetPtr, 188);
if (std::cin.gcount() != 188){return false;} if (std::cin.gcount() != 188){return false;}
DONTEVEN_MSG("Reading from position %llu", bytes); DONTEVEN_MSG("Reading from position %" PRIu64, bytes);
bytes += 188; bytes += 188;
if (!packet.FromPointer(packetPtr)){return false;} if (!packet.FromPointer(packetPtr)){return false;}
if (detail){ if (detail){
@ -74,15 +74,15 @@ bool AnalyserTS::parsePacket(){
} }
AnalyserTS::~AnalyserTS(){ AnalyserTS::~AnalyserTS(){
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++){ for (std::map<size_t, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++){
if ((detail & 1) && (!pidOnly || it->first == pidOnly)){ if ((detail & 1) && (!pidOnly || it->first == pidOnly)){
std::cout << printPES(it->second, it->first); std::cout << printPES(it->second, it->first);
} }
} }
} }
std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){ std::string AnalyserTS::printPES(const std::string &d, size_t PID){
unsigned int headSize = 0; size_t headSize = 0;
std::stringstream res; std::stringstream res;
bool known = false; bool known = false;
res << "[PES " << PID << "]"; res << "[PES " << PID << "]";
@ -98,7 +98,7 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
if (d[0] != 0 || d[1] != 0 || d[2] != 1){ if (d[0] != 0 || d[1] != 0 || d[2] != 1){
res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2] << " ]"; res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2] << " ]";
} }
unsigned int padding = 0; size_t padding = 0;
if (known){ if (known){
if ((d[6] & 0xC0) != 0x80){res << " [!INVALID FIRST BITS!]";} if ((d[6] & 0xC0) != 0x80){res << " [!INVALID FIRST BITS!]";}
if (d[6] & 0x30){res << " [SCRAMBLED]";} if (d[6] & 0x30){res << " [SCRAMBLED]";}
@ -144,19 +144,19 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
res << " [Padding: " << padding << "b]"; res << " [Padding: " << padding << "b]";
} }
if (timeFlags & 0x02){ if (timeFlags & 0x02){
long long unsigned int time = (((unsigned int)d[9] & 0xE) >> 1); uint64_t time = ((d[9] & 0xE) >> 1);
time <<= 15; time <<= 15;
time |= ((unsigned int)d[10] << 7) | (((unsigned int)d[11] >> 1) & 0x7F); time |= ((uint32_t)d[10] << 7) | ((d[11] >> 1) & 0x7F);
time <<= 15; time <<= 15;
time |= ((unsigned int)d[12] << 7) | (((unsigned int)d[13] >> 1) & 0x7F); time |= ((uint32_t)d[12] << 7) | ((d[13] >> 1) & 0x7F);
res << " [PTS " << ((double)time / 90000) << "s]"; res << " [PTS " << ((double)time / 90000) << "s]";
} }
if (timeFlags & 0x01){ if (timeFlags & 0x01){
long long unsigned int time = ((d[14] >> 1) & 0x07); uint64_t time = ((d[14] >> 1) & 0x07);
time <<= 15; time <<= 15;
time |= ((int)d[15] << 7) | (d[16] >> 1); time |= ((uint32_t)d[15] << 7) | (d[16] >> 1);
time <<= 15; time <<= 15;
time |= ((int)d[17] << 7) | (d[18] >> 1); time |= ((uint32_t)d[17] << 7) | (d[18] >> 1);
res << " [DTS " << ((double)time / 90000) << "s]"; res << " [DTS " << ((double)time / 90000) << "s]";
} }
} }
@ -169,12 +169,18 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
res << std::endl; res << std::endl;
if (detail & 32){ if (detail & 32){
unsigned int counter = 0; size_t counter = 0;
for (unsigned int i = 9 + headSize + padding; i < d.size(); ++i){ for (size_t i = 9 + headSize + padding; i < d.size(); ++i){
if ((i < d.size() - 4) && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 0 && d[i + 3] == 1){ if ((i < d.size() - 4) && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 0 && d[i + 3] == 1){
res << std::endl; res << std::endl;
counter = 0; counter = 0;
} }
if ((i < d.size() - 3) && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 1){
if (counter > 1){
res << std::endl << " ";
counter = 0;
}
}
res << std::hex << std::setw(2) << std::setfill('0') << (int)(d[i] & 0xff) << " "; res << std::hex << std::setw(2) << std::setfill('0') << (int)(d[i] & 0xff) << " ";
if ((counter) % 32 == 31){res << std::endl;} if ((counter) % 32 == 31){res << std::endl;}
counter++; counter++;

View file

@ -8,11 +8,11 @@ public:
~AnalyserTS(); ~AnalyserTS();
bool parsePacket(); bool parsePacket();
static void init(Util::Config &conf); static void init(Util::Config &conf);
std::string printPES(const std::string &d, unsigned long PID); std::string printPES(const std::string &d, size_t PID);
private: private:
std::map<unsigned long long, std::string> payloads; std::map<size_t, std::string> payloads;
uint32_t pidOnly; size_t pidOnly;
TS::Packet packet; TS::Packet packet;
uint64_t bytes; uint64_t bytes;
}; };

View file

@ -1,485 +0,0 @@
/// \file dash_analyzer.cpp
/// Contains the code for the DASH Analysing tool.
/// Currently, only mp4 is supported, and the xml parser assumes a representation id tag exists
#include <fstream>
#include <iostream>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
#include <mist/timing.h>
#include <set>
#define OTHER 0x00
#define VIDEO 0x01
#define AUDIO 0x02
///\brief simple struct for storage of stream-specific data
struct StreamData{
long timeScale;
std::string media;
std::string initialization;
std::string initURL;
long trackID;
unsigned int adaptationSet;
unsigned char trackType;
};
StreamData tempSD; // temp global
///\brief another simple structure used for ordering byte seek positions.
struct seekPos{
///\brief Less-than comparison for seekPos structures.
///\param rhs The seekPos to compare with.
///\return Whether this object is smaller than rhs.
bool operator<(const seekPos &rhs) const{
if ((seekTime * rhs.timeScale) < (rhs.seekTime * timeScale)){
return true;
}else{
if ((seekTime * rhs.timeScale) == (rhs.seekTime * timeScale)){
if (adaptationSet < rhs.adaptationSet){
return true;
}else if (adaptationSet == rhs.adaptationSet){
if (trackID < rhs.trackID){return true;}
}
}
}
return false;
}
long timeScale;
long long unsigned int bytePos; /// ?
long long unsigned int seekTime; /// start
long long unsigned int duration; /// duration
unsigned int trackID; /// stores representation ID
unsigned int adaptationSet; /// stores type
unsigned char trackType; /// stores type
std::string url;
};
bool getDelimBlock(std::string &data, std::string name, size_t &blockStart, size_t &blockEnd, std::string delim){
size_t offset = data.find(name);
if (offset == std::string::npos){
return false; // name string not found.
}
// expected: delim character BEFORE blockstart.
offset--;
blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
offset = blockStart + 1; // skip single character!
blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos){
return false; // no start/end quotes found
}
blockEnd++; // include delim
// DEBUG_MSG(DLVL_INFO, "getDelimPos: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true;
}
bool getValueBlock(std::string &data, std::string name, size_t &blockStart, size_t &blockEnd, std::string delim){
size_t offset = data.find(name);
if (offset == std::string::npos){
return false; // name string not found.
}
blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
blockStart++; // clip off quote characters
offset = blockStart; // skip single character!
blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos){
return false; // no start/end quotes found
}
// DEBUG_MSG(DLVL_INFO, "getValueBlock: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true;
}
bool getString(std::string &data, std::string name, std::string &output){
size_t blockStart = 0;
size_t blockEnd = 0;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block.
}
// DEBUG_MSG(DLVL_INFO, "data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) )
output = data.substr(blockStart, (blockEnd - blockStart));
// looks like this function is working as expected
// DEBUG_MSG(DLVL_INFO, "data in getstring %s", (data.substr(blockStart,(blockEnd-blockStart))).c_str());
return true;
}
bool getLong(std::string &data, std::string name, long &output){
size_t blockStart, blockEnd;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block.
}
// DEBUG_MSG(DLVL_INFO, "name: %s data in atol %s",name.c_str(), (data.substr(blockStart,(blockEnd-blockStart))).c_str());
output = atol((data.substr(blockStart, (blockEnd - blockStart))).c_str());
return true;
}
// block expecting separate name and /name occurence, or name and /> before another occurence of <.
bool getBlock(std::string &data, std::string name, int offset, size_t &blockStart, size_t &blockEnd){
blockStart = data.find("<" + name + ">", offset);
if (blockStart == std::string::npos){
blockStart = data.find("<" + name + " ", offset); // this considers both valid situations <name> and <name bla="bla"/>
}
if (blockStart == std::string::npos){
DEBUG_MSG(DLVL_INFO, "no block start found for name: %s at offset: %i", name.c_str(), offset);
return false;
}
blockEnd = data.find("/" + name + ">", blockStart);
if (blockEnd == std::string::npos){
blockEnd = data.find("/>", blockStart);
if (blockEnd == std::string::npos){
DEBUG_MSG(DLVL_INFO, "no block end found.");
return false;
}
size_t temp = data.find("<", blockStart + 1, (blockEnd - blockStart - 1)); // the +1 is to avoid re-interpreting the starting < //TODO!!
if (temp != std::string::npos){// all info is epxected between <name ... />
DEBUG_MSG(DLVL_FAIL, "block start found before block end. offset: %lu block: %s", temp, data.c_str());
return false;
}
// DEBUG_MSG(DLVL_FAIL, "special block end found");
blockEnd += 2; // position after />
}else{
blockEnd += name.size() + 2; // position after /name>
}
// DEBUG_MSG(DLVL_INFO, "getBlock: start: %i end: %i",blockStart,blockEnd);
return true;
}
bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// DEBUG_MSG(DLVL_INFO, "Parsing adaptationSet: %s", data.c_str());
size_t offset = 0;
size_t blockStart, blockEnd;
tempSD.trackType = OTHER;
// get value: mimetype //todo: handle this!
std::string mimeType;
if (!getString(data, "mimeType", mimeType)){// get first occurence of mimeType. --> this will break
// if multiple mimetypes should be read from this block
// because no offset is provided. solution: use this on a substring containing the desired information.
DEBUG_MSG(DLVL_FAIL, "mimeType not found");
return false;
}
DEBUG_MSG(DLVL_INFO, "mimeType: %s", mimeType.c_str()); // checked, OK
if (mimeType.find("video") != std::string::npos){tempSD.trackType = VIDEO;}
if (mimeType.find("audio") != std::string::npos){tempSD.trackType = AUDIO;}
if (tempSD.trackType == OTHER){
DEBUG_MSG(DLVL_FAIL, "no audio or video type found. giving up.");
return false;
}
// find an ID within this adaptationSet block.
if (!getBlock(data, (std::string) "Representation", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "Representation not found");
return false;
}
// representation string
std::string block = data.substr(blockStart, (blockEnd - blockStart));
DEBUG_MSG(DLVL_INFO, "Representation block: %s", block.c_str());
// check if block is not junk?
if (!getLong(block, "id", tempSD.trackID)){
DEBUG_MSG(DLVL_FAIL, "Representation id not found in block %s", block.c_str());
return false;
}
DEBUG_MSG(DLVL_INFO, "Representation/id: %li", tempSD.trackID); // checked, OK
offset = 0;
// get values from SegmentTemplate
if (!getBlock(data, (std::string) "SegmentTemplate", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTemplate not found");
return false;
}
block = data.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "SegmentTemplate block: %s",block.c_str()); //OK
getLong(block, "timescale", tempSD.timeScale);
getString(block, "media", tempSD.media);
getString(block, "initialization", tempSD.initialization);
size_t tmpBlockStart = 0;
size_t tmpBlockEnd = 0;
if (!getDelimBlock(tempSD.media, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s", tempSD.media.c_str());
return false;
}
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.media, "Time", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $Time$ in %s", tempSD.media.c_str());
return false;
}
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.initialization, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s",
tempSD.initialization.c_str());
return false;
}
tempSD.initialization.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
// get segment timeline block from within segment template:
size_t blockOffset = 0; // offset should be 0 because this is a new block
if (!getBlock(block, "SegmentTimeline", blockOffset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTimeline block not found");
return false;
}
std::string block2 = block.substr(blockStart, (blockEnd - blockStart)); // overwrites previous block (takes just the segmentTimeline part
// DEBUG_MSG(DLVL_INFO, "SegmentTimeline block: %s",block2.c_str()); //OK
int numS = 0;
offset = 0;
long long unsigned int totalDuration = 0;
long timeValue;
while (1){
if (!getBlock(block2, "S", offset, blockStart, blockEnd)){
if (numS == 0){
DEBUG_MSG(DLVL_FAIL, "no S found within SegmentTimeline");
return false;
}else{
DEBUG_MSG(DLVL_INFO, "all S found within SegmentTimeline %i", numS);
return true; // break; //escape from while loop (to return true)
}
}
numS++;
// stuff S data into: currentPos
// searching for t(start position)
std::string sBlock = block2.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "S found. offset: %i blockStart: %i blockend: %i block: %s",offset,blockStart, blockEnd, sBlock.c_str()); //OK!
if (getLong(sBlock, "t", timeValue)){
totalDuration = timeValue; // reset totalDuration to value of t
}
if (!getLong(sBlock, "d", timeValue)){// expected duration in every S.
DEBUG_MSG(DLVL_FAIL, "no d found within S");
return false;
}
// stuff data with old value (start of block)
// DEBUG_MSG(DLVL_INFO, "stuffing info from S into set");
seekPos thisPos;
thisPos.trackType = tempSD.trackType;
thisPos.trackID = tempSD.trackID;
thisPos.adaptationSet = tempSD.adaptationSet;
// thisPos.trackID=id;
thisPos.seekTime = totalDuration; // previous total duration is start time of this S.
thisPos.duration = timeValue;
thisPos.timeScale = tempSD.timeScale;
static char charBuf[512];
snprintf(charBuf, 512, tempSD.media.c_str(), tempSD.trackID, totalDuration);
thisPos.url.assign(charBuf);
// DEBUG_MSG(DLVL_INFO, "media url (from rep.ID %d, startTime %d): %s", tempSD.trackID, totalDuration,thisPos.url.c_str());
currentPos.insert(thisPos); // assumes insert copies all data in seekPos struct.
totalDuration += timeValue; // update totalDuration
offset = blockEnd; // blockEnd and blockStart are absolute values within string, offset is not relevant.
}
return true;
}
bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<StreamData> &streamData){
// for all adaptation sets
// representation ID
int numAdaptationSet = 0;
size_t currentOffset = 0;
size_t adaptationSetStart;
size_t adaptationSetEnd;
// DEBUG_MSG(DLVL_INFO, "body received: %s", body.c_str());
while (getBlock(body, "AdaptationSet", currentOffset, adaptationSetStart, adaptationSetEnd)){
tempSD.adaptationSet = numAdaptationSet;
numAdaptationSet++;
DEBUG_MSG(DLVL_INFO, "adaptationSet found. start: %lu end: %lu num: %lu ", adaptationSetStart,
adaptationSetEnd, (adaptationSetEnd - adaptationSetStart));
// get substring: from <adaptationSet... to /adaptationSet>
std::string adaptationSet = body.substr(adaptationSetStart, (adaptationSetEnd - adaptationSetStart));
// function was verified: output as expected.
if (!parseAdaptationSet(adaptationSet, currentPos)){
DEBUG_MSG(DLVL_FAIL, "parseAdaptationSet returned false."); // this also happens in the case
// of OTHER mimetype. in that case it might be desirable to continue searching for valid data instead of quitting.
return false;
}
streamData.push_back(tempSD); // put temp values into adaptation set vector
currentOffset = adaptationSetEnd; // the getblock function should make sure End is at the correct offset.
}
if (numAdaptationSet == 0){
DEBUG_MSG(DLVL_FAIL, "no adaptationSet found.");
return false;
}
DEBUG_MSG(DLVL_INFO, "all adaptation sets found. total: %i", numAdaptationSet);
return true;
}
int main(int argc, char **argv){
Util::Config conf = Util::Config(argv[0]);
conf.addOption("mode",
JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", "
"\"default\":\"analyse\", \"help\":\"What to do with the stream. "
"Valid modes are 'analyse', 'validate', 'output'.\"}"));
conf.addOption("url", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"URL to "
"HLS stream index file to retrieve.\"}"));
conf.addOption("abort", JSON::fromString("{\"long\":\"abort\", \"short\":\"a\", "
"\"arg\":\"integer\", \"default\":-1, \"help\":\"Abort "
"after this many seconds of downloading. Negative "
"values mean unlimited, which is the default.\"}"));
conf.parseArgs(argc, argv);
conf.activate();
unsigned int port = 80;
std::string url = conf.getString("url");
if (url.substr(0, 7) != "http://"){
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://");
return -1;
}
url = url.substr(7); // found problem if url is to short!! it gives out of range when entering http://meh.meh
std::string server = url.substr(0, url.find('/'));
url = url.substr(url.find('/'));
if (server.find(':') != std::string::npos){
port = atoi(server.substr(server.find(':') + 1).c_str());
server = server.substr(0, server.find(':'));
}
long long int startTime = Util::bootSecs();
long long int abortTime = conf.getInteger("abort");
Socket::Connection conn(server, port, false);
// url:
DEBUG_MSG(DLVL_INFO, "url %s server: %s port: %d", url.c_str(), server.c_str(), port);
std::string urlPrependStuff = url.substr(0, url.rfind("/") + 1);
DEBUG_MSG(DLVL_INFO, "prepend stuff: %s", urlPrependStuff.c_str());
if (!conn){conn.open(server, port, false);}
unsigned int pos = 0;
HTTP::Parser H;
H.url = url;
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
H.SendRequest(conn);
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))){}
H.BuildResponse();
std::set<seekPos> currentPos;
std::vector<StreamData> streamData;
// DEBUG_MSG(DLVL_INFO, "body received: %s", H.body.c_str()); //keeps giving empty stuff :(
// DEBUG_MSG(DLVL_INFO, "url %s ", url.c_str());
// std::ifstream in(url.c_str());
// std::string s((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (!parseXML(H.body, currentPos, streamData)){
DEBUG_MSG(DLVL_FAIL, "Manifest parsing failed. body: \n %s", H.body.c_str());
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
return -1;
}
H.Clean();
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "*SUMMARY*");
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++){
DEBUG_MSG(DLVL_INFO, "");
DEBUG_MSG(DLVL_INFO, "ID in vector %d", i);
DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID);
DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet);
DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale);
DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str());
DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str());
}
DEBUG_MSG(DLVL_INFO, "");
for (unsigned int i = 0; i < streamData.size(); i++){// get init url
static char charBuf[512];
snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID);
streamData[i].initURL.assign(charBuf);
DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ",
streamData[i].adaptationSet, streamData[i].trackID, streamData[i].initURL.c_str());
}
while (currentPos.size() && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)){
// DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str());
// match adaptation set and track id?
int tempID = 0;
for (unsigned int i = 0; i < streamData.size(); i++){
if (streamData[i].trackID == currentPos.begin()->trackID &&
streamData[i].adaptationSet == currentPos.begin()->adaptationSet)
tempID = i;
}
if (!conn){conn.open(server, port, false);}
HTTP::Parser H;
H.url = urlPrependStuff;
H.url.append(currentPos.begin()->url);
DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(),
currentPos.begin()->seekTime, currentPos.begin()->seekTime + currentPos.begin()->duration);
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut?
H.SendRequest(conn);
// TODO: get response?
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))){}// ehm...
// std::cout << "leh vomi: "<<H.body <<std::endl;
// DEBUG_MSG(DLVL_INFO, "zut: %s", H.body.c_str());
// strBuf[tempID].append(H.body);
if (!H.body.size()){
DEBUG_MSG(DLVL_FAIL, "No data downloaded from %s", H.url.c_str());
break;
}
size_t beforeParse = H.body.size();
MP4::Box mp4Data;
bool mdatSeen = false;
while (mp4Data.read(H.body)){
if (mp4Data.isType("mdat")){mdatSeen = true;}
}
if (!mdatSeen){
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-(");
break;
}
if (H.body.size()){
DEBUG_MSG(DLVL_FAIL, "%lu bytes left in body. Assuming horrible things...", H.body.size()); //,H.body.c_str());
std::cerr << H.body << std::endl;
if (beforeParse == H.body.size()){break;}
}
H.Clean();
pos = 1000 * (currentPos.begin()->seekTime + currentPos.begin()->duration) / streamData[tempID].timeScale;
if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos){
Util::wait(pos - (Util::bootSecs() - startTime + 5) * 1000);
}
currentPos.erase(currentPos.begin());
}
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
return 0;
}

View file

@ -0,0 +1,78 @@
#include <cstdio>
#include <iostream>
#include <string>
#include <mist/bitfields.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/h264.h>
///\brief Holds everything unique to the analysers.
namespace Analysers{
int analyseH264(Util::Config conf){
FILE *F = fopen(conf.getString("filename").c_str(), "r+b");
if (!F){FAIL_MSG("No such file");}
h264::nalUnit *nalPtr = h264::nalFactory(F);
while (nalPtr){
if (nalPtr->getType() == 0x07){
Utils::bitstream br;
br << nalPtr->payload;
Utils::bitWriter bw;
bw.append(br.get(8), 8); // nalType
bw.append(br.get(8), 8); // profile_idc
bw.append(br.get(8), 8); // constraint flags
bw.append(br.get(8), 8); // level_idc
br.getUExpGolomb();
bw.appendUExpGolomb(0); // seq_parameter_set_id
while (br.size() >= 64){bw.append(br.get(64), 64);}
size_t remainder = br.size();
if (remainder){bw.append(br.get(remainder), remainder);}
nalPtr->payload = bw.str();
}else if (nalPtr->getType() == 0x08){
Utils::bitstream br;
br << nalPtr->payload;
Utils::bitWriter bw;
bw.append(br.get(8), 8); // nalType
br.getUExpGolomb();
bw.appendUExpGolomb(0); // pic_parameter_set_id
br.getUExpGolomb();
bw.appendUExpGolomb(0); // seq_parameter_set_id
while (br.size() >= 64){bw.append(br.get(64), 64);}
size_t remainder = br.size();
if (remainder){bw.append(br.get(remainder), remainder);}
nalPtr->payload = bw.str();
}else if (nalPtr->getType() == 0x01 || nalPtr->getType() == 0x05 || nalPtr->getType() == 0x19){
Utils::bitstream br;
br << nalPtr->payload;
Utils::bitWriter bw;
bw.append(br.get(8), 8); // nalType
bw.appendUExpGolomb(br.getUExpGolomb()); // first_mb_in_slice
bw.appendUExpGolomb(br.getUExpGolomb()); // slice_type
br.getUExpGolomb();
bw.appendUExpGolomb(0); // pic_parameter_set_id
while (br.size() >= 64){bw.append(br.get(64), 64);}
size_t remainder = br.size();
if (remainder){bw.append(br.get(remainder), remainder);}
nalPtr->payload = bw.str();
}
nalPtr->write(std::cout);
delete nalPtr;
nalPtr = h264::nalFactory(F);
}
return 0;
}
}// namespace Analysers
int main(int argc, char **argv){
Util::Config conf = Util::Config(argv[0]);
conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Full "
"path of the file to analyse.\"}"));
conf.parseArgs(argc, argv);
return Analysers::analyseH264(conf);
}

View file

@ -476,6 +476,31 @@ int main_loop(int argc, char **argv){
} }
} }
// Upgrade old configurations
{
bool foundCMAF = false;
bool edit = false;
JSON::Value newVal;
jsonForEach(Controller::Storage["config"]["protocols"], it){
if ((*it)["connector"].asStringRef() == "HSS"){
edit = true;
continue;
}
if ((*it)["connector"].asStringRef() == "DASH"){
edit = true;
continue;
}
if ((*it)["connector"].asStringRef() == "CMAF"){foundCMAF = true;}
newVal.append(*it);
}
if (edit && !foundCMAF){newVal.append(JSON::fromString("{\"connector\":\"CMAF\"}"));}
if (edit){
Controller::Storage["config"]["protocols"] = newVal;
Controller::Log("CONF", "Translated protocols to new versions");
}
}
Controller::Log("CONF", "Controller started"); Controller::Log("CONF", "Controller started");
// Generate instanceId once per boot. // Generate instanceId once per boot.
if (Controller::instanceId == ""){ if (Controller::instanceId == ""){

View file

@ -95,17 +95,11 @@ namespace Controller{
trgs["STREAM_PUSH"]["response"] = "always"; trgs["STREAM_PUSH"]["response"] = "always";
trgs["STREAM_PUSH"]["response_action"] = "If false, rejects the incoming push."; trgs["STREAM_PUSH"]["response_action"] = "If false, rejects the incoming push.";
trgs["STREAM_TRACK_ADD"]["when"] = "Before a new track is accepted by a live stream buffer"; trgs["LIVE_TRACK_LIST"]["when"] = "After the list of valid tracks has been updated";
trgs["STREAM_TRACK_ADD"]["stream_specific"] = true; trgs["LIVE_TRACK_LIST"]["stream_specific"] = true;
trgs["STREAM_TRACK_ADD"]["payload"] = "stream name (string)\ntrack ID (integer)\n"; trgs["LIVE_TRACK_LIST"]["payload"] = "stream name (string)\ntrack list (JSON)\n";
trgs["STREAM_TRACK_ADD"]["response"] = "ignored"; trgs["LIVE_TRACK_LIST"]["response"] = "ignored";
trgs["STREAM_TRACK_ADD"]["response_action"] = "None."; trgs["LIVE_TRACK_LIST"]["response_action"] = "None.";
trgs["STREAM_TRACK_REMOVE"]["when"] = "Before a track is removed by a live stream buffer";
trgs["STREAM_TRACK_REMOVE"]["stream_specific"] = true;
trgs["STREAM_TRACK_REMOVE"]["payload"] = "stream name (string)\ntrack ID (integer)\n";
trgs["STREAM_TRACK_REMOVE"]["response"] = "ignored";
trgs["STREAM_TRACK_REMOVE"]["response_action"] = "None.";
trgs["STREAM_BUFFER"]["when"] = "Every time a live stream buffer changes state"; trgs["STREAM_BUFFER"]["when"] = "Every time a live stream buffer changes state";
trgs["STREAM_BUFFER"]["stream_specific"] = true; trgs["STREAM_BUFFER"]["stream_specific"] = true;

View file

@ -145,17 +145,18 @@ namespace Controller{
if (pipedCapa.isMember("optional")){builPipedPart(p, argarr, argnum, pipedCapa["optional"]);} if (pipedCapa.isMember("optional")){builPipedPart(p, argarr, argnum, pipedCapa["optional"]);}
} }
///\brief Checks current protocol configuration, updates state of enabled connectors if neccessary. ///\brief Checks current protocol configuration, updates state of enabled connectors if
///\param p An object containing all protocols. /// neccessary. \param p An object containing all protocols. \param capabilities An object
///\param capabilities An object containing the detected capabilities. /// containing the detected capabilities. \returns True if any action was taken
///\returns True if any action was taken
/// ///
/// \triggers /// \triggers
/// The `"OUTPUT_START"` trigger is global, and is ran whenever a new protocol listener is started. It cannot be cancelled. Its payload is: /// The `"OUTPUT_START"` trigger is global, and is ran whenever a new protocol listener is
/// started. It cannot be cancelled. Its payload is:
/// ~~~~~~~~~~~~~~~ /// ~~~~~~~~~~~~~~~
/// output listener commandline /// output listener commandline
/// ~~~~~~~~~~~~~~~ /// ~~~~~~~~~~~~~~~
/// The `"OUTPUT_STOP"` trigger is global, and is ran whenever a protocol listener is terminated. It cannot be cancelled. Its payload is: /// The `"OUTPUT_STOP"` trigger is global, and is ran whenever a protocol listener is terminated.
/// It cannot be cancelled. Its payload is:
/// ~~~~~~~~~~~~~~~ /// ~~~~~~~~~~~~~~~
/// output listener commandline /// output listener commandline
/// ~~~~~~~~~~~~~~~ /// ~~~~~~~~~~~~~~~

View file

@ -118,11 +118,12 @@ namespace Controller{
for (unsigned int i = 0; i < 8; ++i){ for (unsigned int i = 0; i < 8; ++i){
aesKey[15 - i] = ((currID >> (i * 8)) + aesKey[15 - i]) & 0xFF; aesKey[15 - i] = ((currID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
} }
char ivec[16];
memset(ivec, 0, 16); Encryption::AES crypter;
crypter.setEncryptKey(aesKey);
// 0 here for 0-filled ivec.
dl.setHeader("X-IRDGAF", dl.setHeader("X-IRDGAF",
Encodings::Base64::encode(Encryption::AES_Crypt( Encodings::Base64::encode(crypter.encryptBlockCTR(0, RELEASE "|" PACKAGE_VERSION)));
RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec)));
} }
if (!dl.get(url) || !dl.isOk()){return;} if (!dl.get(url) || !dl.isOk()){return;}
response = JSON::fromString(dl.data()); response = JSON::fromString(dl.data());
@ -143,11 +144,12 @@ namespace Controller{
aesKey[15 - i] = ((licID >> (i * 8)) + aesKey[15 - i]) & 0xFF; aesKey[15 - i] = ((licID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
} }
std::string cipher = Encodings::Base64::decode(input); std::string cipher = Encodings::Base64::decode(input);
std::string deCrypted;
// magic ivecs, they are empty. It's secretly 16 times \0. // magic ivecs, they are empty. It's secretly 16 times \0.
char ivec[16]; Encryption::AES crypter;
memset(ivec, 0, 16); crypter.setEncryptKey(aesKey);
deCrypted = Encryption::AES_Crypt(cipher.c_str(), cipher.size(), aesKey, ivec); // 0 here for 0-filled ivec.
std::string deCrypted = crypter.encryptBlockCTR(0, cipher);
// get time stamps and license. // get time stamps and license.
// verify checksum // verify checksum

View file

@ -135,7 +135,10 @@ namespace Controller{
void pushCheckLoop(void *np){ void pushCheckLoop(void *np){
{ {
IPC::sharedPage pushReadPage("MstPush", 8 * 1024 * 1024, false, false); IPC::sharedPage pushReadPage("MstPush", 8 * 1024 * 1024, false, false);
if (pushReadPage.mapped){readPushList(pushReadPage.mapped);} if (pushReadPage.mapped){
readPushList(pushReadPage.mapped);
pushReadPage.master = true;
}
} }
pushListRead = true; pushListRead = true;
IPC::sharedPage pushPage("MstPush", 8 * 1024 * 1024, true, false); IPC::sharedPage pushPage("MstPush", 8 * 1024 * 1024, true, false);

View file

@ -30,6 +30,7 @@
#define STAT_CLI_BPS_DOWN 128 #define STAT_CLI_BPS_DOWN 128
#define STAT_CLI_BPS_UP 256 #define STAT_CLI_BPS_UP 256
#define STAT_CLI_CRC 512 #define STAT_CLI_CRC 512
#define STAT_CLI_SESSID 1024
#define STAT_CLI_ALL 0xFFFF #define STAT_CLI_ALL 0xFFFF
// These are used to store "totals" field requests in a bitfield for speedup. // These are used to store "totals" field requests in a bitfield for speedup.
#define STAT_TOT_CLIENTS 1 #define STAT_TOT_CLIENTS 1
@ -48,6 +49,8 @@ std::map<std::string, Controller::triggerLog> Controller::triggerStats; ///< Hol
bool Controller::killOnExit = KILL_ON_EXIT; bool Controller::killOnExit = KILL_ON_EXIT;
tthread::mutex Controller::statsMutex; tthread::mutex Controller::statsMutex;
unsigned int Controller::maxConnsPerIP = 0; unsigned int Controller::maxConnsPerIP = 0;
uint64_t Controller::statDropoff = 0;
char noBWCountMatches[1717]; char noBWCountMatches[1717];
uint64_t bwLimit = 128 * 1024 * 1024; // gigabit default limit uint64_t bwLimit = 128 * 1024 * 1024; // gigabit default limit
@ -96,35 +99,27 @@ static uint64_t servInputs = 0;
static uint64_t servOutputs = 0; static uint64_t servOutputs = 0;
static uint64_t servViewers = 0; static uint64_t servViewers = 0;
Controller::sessIndex::sessIndex(std::string dhost, unsigned int dcrc, std::string dstreamName,
std::string dconnector){
ID = "UNSET";
host = dhost;
crc = dcrc;
streamName = dstreamName;
connector = dconnector;
}
Controller::sessIndex::sessIndex(){ Controller::sessIndex::sessIndex(){
crc = 0; crc = 0;
} }
/// Initializes a sessIndex from a statistics object + index, converting binary format IP addresses
/// into strings. This extracts the host, stream name, connector and crc field, ignoring everything
/// else.
Controller::sessIndex::sessIndex(const Comms::Statistics &statComm, size_t id){
host = statComm.getHost(id);
streamName = statComm.getStream(id);
connector = statComm.getConnector(id);
crc = statComm.getCRC(id);
ID = statComm.getSessId(id);
}
std::string Controller::sessIndex::toStr(){ std::string Controller::sessIndex::toStr(){
std::stringstream s; std::stringstream s;
s << ID << "(" << host << " " << crc << " " << streamName << " " << connector << ")"; s << ID << "(" << host << " " << crc << " " << streamName << " " << connector << ")";
return s.str(); return s.str();
} }
/// Initializes a sessIndex from a statExchange object, converting binary format IP addresses into
/// strings. This extracts the host, stream name, connector and crc field, ignoring everything else.
Controller::sessIndex::sessIndex(IPC::statExchange &data){
Socket::hostBytesToStr(data.host().c_str(), 16, host);
streamName = data.streamName();
connector = data.connector();
crc = data.crc();
ID = data.getSessId();
}
bool Controller::sessIndex::operator==(const Controller::sessIndex &b) const{ bool Controller::sessIndex::operator==(const Controller::sessIndex &b) const{
return (host == b.host && crc == b.crc && streamName == b.streamName && connector == b.connector); return (host == b.host && crc == b.crc && streamName == b.streamName && connector == b.connector);
} }
@ -166,13 +161,13 @@ void Controller::streamStopped(std::string stream){
INFO_MSG("Stream %s became inactive", stream.c_str()); INFO_MSG("Stream %s became inactive", stream.c_str());
} }
/// \todo Make this prettier. Comms::Statistics statComm;
IPC::sharedServer *statPointer = 0; bool statCommActive = false;
/// Invalidates all current sessions for the given streamname /// Invalidates all current sessions for the given streamname
/// Updates the session cache, afterwards. /// Updates the session cache, afterwards.
void Controller::sessions_invalidate(const std::string &streamname){ void Controller::sessions_invalidate(const std::string &streamname){
if (!statPointer){ if (!statCommActive){
FAIL_MSG("In shutdown procedure - cannot invalidate sessions."); FAIL_MSG("In shutdown procedure - cannot invalidate sessions.");
return; return;
} }
@ -209,7 +204,7 @@ void Controller::sessions_shutdown(JSON::Iter &i){
/// Shuts down the given session /// Shuts down the given session
/// Updates the session cache, afterwards. /// Updates the session cache, afterwards.
void Controller::sessId_shutdown(const std::string &sessId){ void Controller::sessId_shutdown(const std::string &sessId){
if (!statPointer){ if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions."); FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions.");
return; return;
} }
@ -231,7 +226,7 @@ void Controller::sessId_shutdown(const std::string &sessId){
/// Tags the given session /// Tags the given session
void Controller::sessId_tag(const std::string &sessId, const std::string &tag){ void Controller::sessId_tag(const std::string &sessId, const std::string &tag){
if (!statPointer){ if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot tag sessions."); FAIL_MSG("In controller shutdown procedure - cannot tag sessions.");
return; return;
} }
@ -250,7 +245,7 @@ void Controller::sessId_tag(const std::string &sessId, const std::string &tag){
/// Shuts down sessions with the given tag set /// Shuts down sessions with the given tag set
/// Updates the session cache, afterwards. /// Updates the session cache, afterwards.
void Controller::tag_shutdown(const std::string &tag){ void Controller::tag_shutdown(const std::string &tag){
if (!statPointer){ if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions."); FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions.");
return; return;
} }
@ -272,7 +267,7 @@ void Controller::tag_shutdown(const std::string &tag){
/// Shuts down all current sessions for the given streamname /// Shuts down all current sessions for the given streamname
/// Updates the session cache, afterwards. /// Updates the session cache, afterwards.
void Controller::sessions_shutdown(const std::string &streamname, const std::string &protocol){ void Controller::sessions_shutdown(const std::string &streamname, const std::string &protocol){
if (!statPointer){ if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions."); FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions.");
return; return;
} }
@ -325,9 +320,13 @@ void Controller::writeSessionCache(){
/// old statistics that have disconnected over 10 minutes ago. /// old statistics that have disconnected over 10 minutes ago.
void Controller::SharedMemStats(void *config){ void Controller::SharedMemStats(void *config){
HIGH_MSG("Starting stats thread"); HIGH_MSG("Starting stats thread");
IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true); statComm.reload(true);
statPointer = &statServer; statCommActive = true;
shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, true); shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, false, false);
if (!shmSessions || !shmSessions->mapped){
if (shmSessions){delete shmSessions;}
shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, true);
}
cacheLock = new IPC::semaphore(SEM_SESSCACHE, O_CREAT | O_RDWR, ACCESSPERMS, 1); cacheLock = new IPC::semaphore(SEM_SESSCACHE, O_CREAT | O_RDWR, ACCESSPERMS, 1);
cacheLock->unlink(); cacheLock->unlink();
cacheLock->open(SEM_SESSCACHE, O_CREAT | O_RDWR, ACCESSPERMS, 1); cacheLock->open(SEM_SESSCACHE, O_CREAT | O_RDWR, ACCESSPERMS, 1);
@ -341,7 +340,10 @@ void Controller::SharedMemStats(void *config){
tthread::lock_guard<tthread::mutex> guard2(statsMutex); tthread::lock_guard<tthread::mutex> guard2(statsMutex);
cacheLock->wait(); /*LTS*/ cacheLock->wait(); /*LTS*/
// parse current users // parse current users
statServer.parseEach(parseStatistics); statLeadIn();
COMM_LOOP(statComm, statOnActive(id), statOnDisconnect(id));
statLeadOut();
if (firstRun){ if (firstRun){
firstRun = false; firstRun = false;
servUpOtherBytes = 0; servUpOtherBytes = 0;
@ -357,18 +359,27 @@ void Controller::SharedMemStats(void *config){
// wipe old statistics // wipe old statistics
if (sessions.size()){ if (sessions.size()){
std::list<sessIndex> mustWipe; std::list<sessIndex> mustWipe;
unsigned long long cutOffPoint = Util::epoch() - STAT_CUTOFF; uint64_t cutOffPoint = Util::bootSecs() - STAT_CUTOFF;
unsigned long long disconnectPointIn = Util::epoch() - STATS_INPUT_DELAY; uint64_t disconnectPointIn = Util::bootSecs() - STATS_INPUT_DELAY;
unsigned long long disconnectPointOut = Util::epoch() - STATS_DELAY; uint64_t disconnectPointOut = Util::bootSecs() - STATS_DELAY;
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){ for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
unsigned long long dPoint = it->second.getSessType() == SESS_INPUT ? disconnectPointIn : disconnectPointOut; uint64_t dPoint = it->second.getSessType() == SESS_INPUT ? disconnectPointIn : disconnectPointOut;
it->second.ping(it->first, dPoint);
if (it->second.sync == 100){ if (it->second.sync == 100){
// Denied entries are connection-entry-wiped as soon as they become boring
it->second.wipeOld(dPoint); it->second.wipeOld(dPoint);
}else{ }else{
// Normal entries are summarized after STAT_CUTOFF seconds
it->second.wipeOld(cutOffPoint); it->second.wipeOld(cutOffPoint);
} }
if (!it->second.hasData()){mustWipe.push_back(it->first);} // This part handles ending sessions, keeping them in cache for now
if (it->second.isTracked() && !it->second.isConnected() && it->second.getEnd() < dPoint){
it->second.dropSession(it->first);
}
// This part handles wiping from the session cache
if (!it->second.hasData()){
it->second.dropSession(it->first); // End the session, just in case it wasn't yet
mustWipe.push_back(it->first);
}
} }
while (mustWipe.size()){ while (mustWipe.size()){
sessions.erase(mustWipe.front()); sessions.erase(mustWipe.front());
@ -429,15 +440,18 @@ void Controller::SharedMemStats(void *config){
} }
Util::wait(1000); Util::wait(1000);
} }
statPointer = 0; statCommActive = false;
HIGH_MSG("Stopping stats thread"); HIGH_MSG("Stopping stats thread");
if (Util::Config::is_restarting){ if (Util::Config::is_restarting){
statServer.abandon(); statComm.setMaster(false);
shmSessions->master = false; shmSessions->master = false;
}else{/*LTS-START*/ }else{/*LTS-START*/
if (Controller::killOnExit){ if (Controller::killOnExit){
WARN_MSG("Killing all connected clients to force full shutdown"); WARN_MSG("Killing all connected clients to force full shutdown");
statServer.finishEach(); for (uint32_t id = statComm.firstValid(); id != statComm.endValid(); id++){
if (statComm.getStatus(id) == COMM_STATUS_INVALID){continue;}
statComm.kill(id, true);
}
} }
/*LTS-END*/ /*LTS-END*/
} }
@ -474,12 +488,10 @@ std::set<std::string> Controller::getActiveStreams(const std::string &prefix){
uint32_t Controller::statSession::invalidate(){ uint32_t Controller::statSession::invalidate(){
uint32_t ret = 0; uint32_t ret = 0;
sync = 1; sync = 1;
if (curConns.size() && statPointer){ if (curConns.size() && statCommActive){
for (std::map<uint64_t, statStorage>::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){ for (std::map<uint64_t, statStorage>::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){
char *data = statPointer->getIndex(jt->first); if (statComm.getStatus(jt->first) != COMM_STATUS_INVALID){
if (data){ statComm.setSync(2, jt->first);
IPC::statExchange tmpEx(data);
tmpEx.setSync(2);
ret++; ret++;
} }
} }
@ -492,16 +504,14 @@ uint32_t Controller::statSession::invalidate(){
uint32_t Controller::statSession::kill(){ uint32_t Controller::statSession::kill(){
uint32_t ret = 0; uint32_t ret = 0;
sync = 100; sync = 100;
if (curConns.size() && statPointer){ if (curConns.size() && statCommActive){
for (std::map<uint64_t, statStorage>::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){ for (std::map<uint64_t, statStorage>::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){
char *data = statPointer->getIndex(jt->first); if (statComm.getStatus(jt->first) != COMM_STATUS_INVALID){
if (data){ statComm.setSync(100, jt->first);
IPC::statExchange tmpEx(data); uint32_t pid = statComm.getPid(jt->first);
tmpEx.setSync(100);
uint32_t pid = tmpEx.getPID();
if (pid > 1){ if (pid > 1){
Util::Procs::Stop(pid); Util::Procs::Stop(pid);
INFO_MSG("Killing PID %lu", pid); INFO_MSG("Killing PID %" PRIu32, pid);
} }
ret++; ret++;
} }
@ -511,15 +521,18 @@ uint32_t Controller::statSession::kill(){
} }
/// Updates the given active connection with new stats data. /// Updates the given active connection with new stats data.
void Controller::statSession::update(uint64_t index, IPC::statExchange &data){ void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm){
// update the sync byte: 0 = requesting fill, 2 = requesting refill, 1 = needs checking, > 2 = state known (100=denied, 10=accepted) std::string myHost = statComm.getHost(index);
if (!data.getSync()){ std::string myStream = statComm.getStream(index);
sessIndex tmpidx(data); std::string myConnector = statComm.getConnector(index);
std::string myHost = tmpidx.host; // update the sync byte: 0 = requesting fill, 2 = requesting refill, 1 = needs checking, > 2 =
// state known (100=denied, 10=accepted)
if (!statComm.getSync(index)){
sessIndex tmpidx(statComm, index);
// if we have a maximum connection count per IP, enforce it // if we have a maximum connection count per IP, enforce it
if (maxConnsPerIP && !data.getSync()){ if (maxConnsPerIP && !statComm.getSync(index)){
unsigned int currConns = 1; unsigned int currConns = 1;
long long shortly = Util::epoch(); long long shortly = Util::bootSecs();
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){ for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
if (&it->second != this && it->first.host == myHost && if (&it->second != this && it->first.host == myHost &&
@ -533,35 +546,35 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
} }
if (currConns > maxConnsPerIP){ if (currConns > maxConnsPerIP){
WARN_MSG("Disconnecting session from %s: exceeds max connection count of %u", myHost.c_str(), maxConnsPerIP); WARN_MSG("Disconnecting session from %s: exceeds max connection count of %u", myHost.c_str(), maxConnsPerIP);
data.setSync(100); statComm.setSync(100, index);
} }
} }
if (data.getSync() != 100){ if (statComm.getSync(index) != 100){
// only set the sync if this is the first connection in the list // only set the sync if this is the first connection in the list
// we also catch the case that there are no connections, which is an error-state // we also catch the case that there are no connections, which is an error-state
if (!sessions[tmpidx].curConns.size() || sessions[tmpidx].curConns.begin()->first == index){ if (!sessions[tmpidx].curConns.size() || sessions[tmpidx].curConns.begin()->first == index){
MEDIUM_MSG("Requesting sync to %u for %s, %s, %s, %lu", sync, data.streamName().c_str(), MEDIUM_MSG("Requesting sync to %u for %s, %s, %s, %" PRIu32, sync, myStream.c_str(),
data.connector().c_str(), myHost.c_str(), data.crc() & 0xFFFFFFFFu); myConnector.c_str(), myHost.c_str(), statComm.getCRC(index) & 0xFFFFFFFFu);
data.setSync(sync); statComm.setSync(sync, index);
} }
// and, always set the sync if it is > 2 // and, always set the sync if it is > 2
if (sync > 2){ if (sync > 2){
MEDIUM_MSG("Setting sync to %u for %s, %s, %s, %lu", sync, data.streamName().c_str(), MEDIUM_MSG("Setting sync to %u for %s, %s, %s, %" PRIu32, sync, myStream.c_str(),
data.connector().c_str(), myHost.c_str(), data.crc() & 0xFFFFFFFFu); myConnector.c_str(), myHost.c_str(), statComm.getCRC(index) & 0xFFFFFFFFu);
data.setSync(sync); statComm.setSync(sync, index);
} }
} }
}else{ }else{
if (sync < 2 && data.getSync() > 2){sync = data.getSync();} if (sync < 2 && statComm.getSync(index) > 2){sync = statComm.getSync(index);}
} }
long long prevDown = getDown(); long long prevDown = getDown();
long long prevUp = getUp(); long long prevUp = getUp();
curConns[index].update(data); curConns[index].update(statComm, index);
// store timestamp of first received data, if older // store timestamp of first received data, if older
if (firstSec > data.now()){firstSec = data.now();} if (firstSec > statComm.getNow(index)){firstSec = statComm.getNow(index);}
// store timestamp of last received data, if newer // store timestamp of last received data, if newer
if (data.now() > lastSec){ if (statComm.getNow(index) > lastSec){
lastSec = data.now(); lastSec = statComm.getNow(index);
if (!tracked){ if (!tracked){
tracked = true; tracked = true;
firstActive = firstSec; firstActive = firstSec;
@ -571,13 +584,13 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
long long currUp = getUp(); long long currUp = getUp();
if (currUp - prevUp < 0 || currDown - prevDown < 0){ if (currUp - prevUp < 0 || currDown - prevDown < 0){
INFO_MSG("Negative data usage! %lldu/%lldd (u%lld->%lld) in %s over %s, #%lu", currUp - prevUp, INFO_MSG("Negative data usage! %lldu/%lldd (u%lld->%lld) in %s over %s, #%lu", currUp - prevUp,
currDown - prevDown, prevUp, currUp, data.streamName().c_str(), data.connector().c_str(), index); currDown - prevDown, prevUp, currUp, myStream.c_str(), myConnector.c_str(), index);
}else{ }else{
if (!noBWCount){ if (!noBWCount){
size_t bwMatchOffset = 0; size_t bwMatchOffset = 0;
noBWCount = 1; noBWCount = 1;
while (noBWCountMatches[bwMatchOffset + 16] != 0 && bwMatchOffset < 1700){ while (noBWCountMatches[bwMatchOffset + 16] != 0 && bwMatchOffset < 1700){
if (Socket::matchIPv6Addr(data.host(), std::string(noBWCountMatches + bwMatchOffset, 16), if (Socket::matchIPv6Addr(statComm.getHost(index), std::string(noBWCountMatches + bwMatchOffset, 16),
noBWCountMatches[bwMatchOffset + 16])){ noBWCountMatches[bwMatchOffset + 16])){
noBWCount = 2; noBWCount = 2;
break; break;
@ -599,40 +612,39 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
} }
} }
if (currDown + currUp >= COUNTABLE_BYTES){ if (currDown + currUp >= COUNTABLE_BYTES){
std::string streamName = data.streamName();
if (sessionType == SESS_UNSET){ if (sessionType == SESS_UNSET){
if (data.connector() == "INPUT"){ if (myConnector == "INPUT"){
++servInputs; ++servInputs;
streamStats[streamName].inputs++; streamStats[myStream].inputs++;
streamStats[streamName].currIns++; streamStats[myStream].currIns++;
sessionType = SESS_INPUT; sessionType = SESS_INPUT;
}else if (data.connector() == "OUTPUT"){ }else if (myConnector == "OUTPUT"){
++servOutputs; ++servOutputs;
streamStats[streamName].outputs++; streamStats[myStream].outputs++;
streamStats[streamName].currOuts++; streamStats[myStream].currOuts++;
sessionType = SESS_OUTPUT; sessionType = SESS_OUTPUT;
}else{ }else{
++servViewers; ++servViewers;
streamStats[streamName].viewers++; streamStats[myStream].viewers++;
streamStats[streamName].currViews++; streamStats[myStream].currViews++;
sessionType = SESS_VIEWER; sessionType = SESS_VIEWER;
} }
} }
// If previous < COUNTABLE_BYTES, we haven't counted any data so far. // If previous < COUNTABLE_BYTES, we haven't counted any data so far.
// We need to count all the data in that case, otherwise we only count the difference. // We need to count all the data in that case, otherwise we only count the difference.
if (prevUp + prevDown < COUNTABLE_BYTES){ if (prevUp + prevDown < COUNTABLE_BYTES){
if (!streamName.size() || streamName[0] == 0){ if (!myStream.size() || myStream[0] == 0){
if (streamStats.count(streamName)){streamStats.erase(streamName);} if (streamStats.count(myStream)){streamStats.erase(myStream);}
}else{ }else{
streamStats[streamName].upBytes += currUp; streamStats[myStream].upBytes += currUp;
streamStats[streamName].downBytes += currDown; streamStats[myStream].downBytes += currDown;
} }
}else{ }else{
if (!streamName.size() || streamName[0] == 0){ if (!myStream.size() || myStream[0] == 0){
if (streamStats.count(streamName)){streamStats.erase(streamName);} if (streamStats.count(myStream)){streamStats.erase(myStream);}
}else{ }else{
streamStats[streamName].upBytes += currUp - prevUp; streamStats[myStream].upBytes += currUp - prevUp;
streamStats[streamName].downBytes += currDown - prevDown; streamStats[myStream].downBytes += currDown - prevDown;
} }
} }
} }
@ -642,7 +654,7 @@ Controller::sessType Controller::statSession::getSessType(){
return sessionType; return sessionType;
} }
/// Archives the given connection. /// Archives connection log entries older than the given cutOff point.
void Controller::statSession::wipeOld(uint64_t cutOff){ void Controller::statSession::wipeOld(uint64_t cutOff){
if (firstSec > cutOff){return;} if (firstSec > cutOff){return;}
firstSec = 0xFFFFFFFFFFFFFFFFull; firstSec = 0xFFFFFFFFFFFFFFFFull;
@ -673,76 +685,74 @@ void Controller::statSession::wipeOld(uint64_t cutOff){
} }
} }
void Controller::statSession::ping(const Controller::sessIndex &index, uint64_t disconnectPoint){ void Controller::statSession::dropSession(const Controller::sessIndex &index){
if (!tracked || curConns.size()){return;} if (!tracked || curConns.size()){return;}
if (lastSec < disconnectPoint){ switch (sessionType){
switch (sessionType){ case SESS_INPUT:
case SESS_INPUT: if (streamStats[index.streamName].currIns){streamStats[index.streamName].currIns--;}
if (streamStats[index.streamName].currIns){streamStats[index.streamName].currIns--;} break;
break; case SESS_OUTPUT:
case SESS_OUTPUT: if (streamStats[index.streamName].currOuts){streamStats[index.streamName].currOuts--;}
if (streamStats[index.streamName].currOuts){streamStats[index.streamName].currOuts--;} break;
break; case SESS_VIEWER:
case SESS_VIEWER: if (streamStats[index.streamName].currViews){streamStats[index.streamName].currViews--;}
if (streamStats[index.streamName].currViews){streamStats[index.streamName].currViews--;} break;
break; default: break;
default: break;
}
uint64_t duration = lastSec - firstActive;
if (duration < 1){duration = 1;}
std::stringstream tagStream;
if (tags.size()){
for (std::set<std::string>::iterator it = tags.begin(); it != tags.end(); ++it){
tagStream << "[" << *it << "]";
}
}
Controller::logAccess(index.ID, index.streamName, index.connector, index.host, duration,
getUp(), getDown(), tagStream.str());
if (Controller::accesslog.size()){
if (Controller::accesslog == "LOG"){
std::stringstream accessStr;
accessStr << "Session <" << index.ID << "> " << index.streamName << " (" << index.connector
<< ") from " << index.host << " ended after " << duration << "s, avg "
<< getUp() / duration / 1024 << "KB/s up " << getDown() / duration / 1024 << "KB/s down.";
if (tags.size()){accessStr << " Tags: " << tagStream.str();}
Controller::Log("ACCS", accessStr.str());
}else{
static std::ofstream accLogFile;
static std::string accLogFileName;
if (accLogFileName != Controller::accesslog || !accLogFile.good()){
accLogFile.close();
accLogFile.open(Controller::accesslog.c_str(), std::ios_base::app);
if (!accLogFile.good()){
FAIL_MSG("Could not open access log file '%s': %s", Controller::accesslog.c_str(), strerror(errno));
}else{
accLogFileName = Controller::accesslog;
}
}
if (accLogFile.good()){
time_t rawtime;
struct tm *timeinfo;
struct tm tmptime;
char buffer[100];
time(&rawtime);
timeinfo = localtime_r(&rawtime, &tmptime);
strftime(buffer, 100, "%F %H:%M:%S", timeinfo);
accLogFile << buffer << ", " << index.ID << ", " << index.streamName << ", "
<< index.connector << ", " << index.host << ", " << duration << ", "
<< getUp() / duration / 1024 << ", " << getDown() / duration / 1024 << ", ";
if (tags.size()){accLogFile << tagStream.str();}
accLogFile << std::endl;
}
}
}
tracked = false;
firstActive = 0;
firstSec = 0xFFFFFFFFFFFFFFFFull;
lastSec = 0;
wipedUp = 0;
wipedDown = 0;
oldConns.clear();
sessionType = SESS_UNSET;
} }
uint64_t duration = lastSec - firstActive;
if (duration < 1){duration = 1;}
std::stringstream tagStream;
if (tags.size()){
for (std::set<std::string>::iterator it = tags.begin(); it != tags.end(); ++it){
tagStream << "[" << *it << "]";
}
}
Controller::logAccess(index.ID, index.streamName, index.connector, index.host, duration, getUp(),
getDown(), tagStream.str());
if (Controller::accesslog.size()){
if (Controller::accesslog == "LOG"){
std::stringstream accessStr;
accessStr << "Session <" << index.ID << "> " << index.streamName << " (" << index.connector
<< ") from " << index.host << " ended after " << duration << "s, avg "
<< getUp() / duration / 1024 << "KB/s up " << getDown() / duration / 1024 << "KB/s down.";
if (tags.size()){accessStr << " Tags: " << tagStream.str();}
Controller::Log("ACCS", accessStr.str());
}else{
static std::ofstream accLogFile;
static std::string accLogFileName;
if (accLogFileName != Controller::accesslog || !accLogFile.good()){
accLogFile.close();
accLogFile.open(Controller::accesslog.c_str(), std::ios_base::app);
if (!accLogFile.good()){
FAIL_MSG("Could not open access log file '%s': %s", Controller::accesslog.c_str(), strerror(errno));
}else{
accLogFileName = Controller::accesslog;
}
}
if (accLogFile.good()){
time_t rawtime;
struct tm *timeinfo;
struct tm tmptime;
char buffer[100];
time(&rawtime);
timeinfo = localtime_r(&rawtime, &tmptime);
strftime(buffer, 100, "%F %H:%M:%S", timeinfo);
accLogFile << buffer << ", " << index.ID << ", " << index.streamName << ", "
<< index.connector << ", " << index.host << ", " << duration << ", "
<< getUp() / duration / 1024 << ", " << getDown() / duration / 1024 << ", ";
if (tags.size()){accLogFile << tagStream.str();}
accLogFile << std::endl;
}
}
}
tracked = false;
firstActive = 0;
firstSec = 0xFFFFFFFFFFFFFFFFull;
lastSec = 0;
wipedUp = 0;
wipedDown = 0;
oldConns.clear();
sessionType = SESS_UNSET;
} }
/// Archives the given connection. /// Archives the given connection.
@ -836,16 +846,12 @@ bool Controller::statSession::hasDataFor(uint64_t t){
/// Returns true if there is any data for this session. /// Returns true if there is any data for this session.
bool Controller::statSession::hasData(){ bool Controller::statSession::hasData(){
if (!firstSec && !lastSec){return false;} if (!firstSec && !lastSec){return false;}
if (curConns.size()){return true;}
if (oldConns.size()){ if (oldConns.size()){
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){ for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
if (it->log.size()){return true;} if (it->log.size()){return true;}
} }
} }
if (curConns.size()){
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
if (it->second.log.size()){return true;}
}
}
return false; return false;
} }
@ -854,26 +860,14 @@ bool Controller::statSession::isViewerOn(uint64_t t){
return getUp(t) + getDown(t) > COUNTABLE_BYTES; return getUp(t) + getDown(t) > COUNTABLE_BYTES;
} }
/// Returns true if this session should count as a viewer /// Returns true if this session should be considered connected
bool Controller::statSession::isViewer(){ bool Controller::statSession::isConnected(){
long long upTotal = wipedUp + wipedDown; return curConns.size();
if (oldConns.size()){ }
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
if (it->log.size()){ /// Returns true if this session has started (tracked == true) but not yet ended (log entry written)
upTotal += it->log.rbegin()->second.up + it->log.rbegin()->second.down; bool Controller::statSession::isTracked(){
if (upTotal > COUNTABLE_BYTES){return true;} return tracked;
}
}
}
if (curConns.size()){
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
if (it->second.log.size()){
upTotal += it->second.log.rbegin()->second.up + it->second.log.rbegin()->second.down;
if (upTotal > COUNTABLE_BYTES){return true;}
}
}
}
return false;
} }
/// Returns the cumulative connected time for this session at timestamp t. /// Returns the cumulative connected time for this session at timestamp t.
@ -1014,58 +1008,60 @@ Controller::statLog &Controller::statStorage::getDataFor(unsigned long long t){
/// This function is called by parseStatistics. /// This function is called by parseStatistics.
/// It updates the internally saved statistics data. /// It updates the internally saved statistics data.
void Controller::statStorage::update(IPC::statExchange &data){ void Controller::statStorage::update(Comms::Statistics &statComm, size_t index){
statLog tmp; statLog tmp;
tmp.time = data.time(); tmp.time = statComm.getTime(index);
tmp.lastSecond = data.lastSecond(); tmp.lastSecond = statComm.getLastSecond(index);
tmp.down = data.down(); tmp.down = statComm.getDown(index);
tmp.up = data.up(); tmp.up = statComm.getUp(index);
log[data.now()] = tmp; log[statComm.getNow(index)] = tmp;
// wipe data older than approx. STAT_CUTOFF seconds // wipe data older than approx. STAT_CUTOFF seconds
/// \todo Remove least interesting data first. /// \todo Remove least interesting data first.
if (log.size() > STAT_CUTOFF){log.erase(log.begin());} if (log.size() > STAT_CUTOFF){log.erase(log.begin());}
} }
/// This function is called by the shared memory page that holds statistics. void Controller::statLeadIn(){
/// It updates the internally saved statistics data, moving across sessions or archiving when necessary. statDropoff = Util::bootSecs() - 3;
void Controller::parseStatistics(char *data, size_t len, uint32_t id){ }
// retrieve stats data void Controller::statOnActive(size_t id){
IPC::statExchange tmpEx(data);
// calculate the current session index, store as idx. // calculate the current session index, store as idx.
sessIndex idx(tmpEx); sessIndex idx(statComm, id);
// if the connection was already indexed and it has changed, move it
if (connToSession.count(id) && connToSession[id] != idx){ if (statComm.getNow(id) >= statDropoff){
if (sessions[connToSession[id]].getSessType() != SESS_UNSET){ // if the connection was already indexed and it has changed, move it
INFO_MSG("Switching connection %" PRIu32 " from active session %s over to %s", id, if (connToSession.count(id) && connToSession[id] != idx){
connToSession[id].toStr().c_str(), idx.toStr().c_str()); if (sessions[connToSession[id]].getSessType() != SESS_UNSET){
}else{ INFO_MSG("Switching connection %zu from active session %s over to %s", id,
INFO_MSG("Switching connection %" PRIu32 " from inactive session %s over to %s", id, connToSession[id].toStr().c_str(), idx.toStr().c_str());
connToSession[id].toStr().c_str(), idx.toStr().c_str()); }else{
INFO_MSG("Switching connection %zu from inactive session %s over to %s", id,
connToSession[id].toStr().c_str(), idx.toStr().c_str());
}
sessions[connToSession[id]].switchOverTo(sessions[idx], id);
// Destroy this session without calling dropSession, because it was merged into another. What session? We never made it. Stop asking hard questions. Go, shoo. *sprays water*
if (!sessions[connToSession[id]].hasData()){sessions.erase(connToSession[id]);}
} }
sessions[connToSession[id]].switchOverTo(sessions[idx], id); if (!connToSession.count(id)){
if (!sessions[connToSession[id]].hasData()){sessions.erase(connToSession[id]);} INSANE_MSG("New connection: %zu as %s", id, idx.toStr().c_str());
} }
if (!connToSession.count(id)){ // store the index for later comparison
INSANE_MSG("New connection: %" PRIu32 " as %s", id, idx.toStr().c_str()); connToSession[id] = idx;
} // update the session with the latest data
// store the index for later comparison sessions[idx].update(id, statComm);
connToSession[id] = idx;
// update the session with the latest data
sessions[idx].update(id, tmpEx);
// check validity of stats data
char counter = (*(data - 1)) & 0x7F;
if (counter == 126 || counter == 127){
// the data is no longer valid - connection has gone away, store for later
INSANE_MSG("Ended connection: %" PRIu32 " as %s", id, idx.toStr().c_str());
sessions[idx].finish(id);
connToSession.erase(id);
} }
} }
void Controller::statOnDisconnect(size_t id){
sessIndex idx(statComm, id);
INSANE_MSG("Ended connection: %zu as %s", id, idx.toStr().c_str());
sessions[idx].finish(id);
connToSession.erase(id);
}
void Controller::statLeadOut(){}
/// Returns true if this stream has at least one connected client. /// Returns true if this stream has at least one connected client.
bool Controller::hasViewers(std::string streamName){ bool Controller::hasViewers(std::string streamName){
if (sessions.size()){ if (sessions.size()){
long long currTime = Util::epoch(); long long currTime = Util::bootSecs();
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){ for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
if (it->first.streamName == streamName && if (it->first.streamName == streamName &&
(it->second.hasDataFor(currTime) || it->second.hasDataFor(currTime - 1))){ (it->second.hasDataFor(currTime) || it->second.hasDataFor(currTime - 1))){
@ -1119,7 +1115,11 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
// to make sure no nasty timing business takes place, we store the case "now" as a bool. // to make sure no nasty timing business takes place, we store the case "now" as a bool.
bool now = (reqTime == 0); bool now = (reqTime == 0);
// add the current time, if negative or zero. // add the current time, if negative or zero.
if (reqTime <= 0){reqTime += Util::epoch();} if (reqTime <= 0){
reqTime += Util::bootSecs();
}else{
reqTime -= (Util::epoch() - Util::bootSecs());
}
// at this point, reqTime is the absolute timestamp. // at this point, reqTime is the absolute timestamp.
rep["time"] = reqTime; // fill the absolute timestamp rep["time"] = reqTime; // fill the absolute timestamp
@ -1136,6 +1136,7 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
if ((*it).asStringRef() == "up"){fields |= STAT_CLI_UP;} if ((*it).asStringRef() == "up"){fields |= STAT_CLI_UP;}
if ((*it).asStringRef() == "downbps"){fields |= STAT_CLI_BPS_DOWN;} if ((*it).asStringRef() == "downbps"){fields |= STAT_CLI_BPS_DOWN;}
if ((*it).asStringRef() == "upbps"){fields |= STAT_CLI_BPS_UP;} if ((*it).asStringRef() == "upbps"){fields |= STAT_CLI_BPS_UP;}
if ((*it).asStringRef() == "sessid"){fields |= STAT_CLI_SESSID;}
} }
} }
// select all, if none selected // select all, if none selected
@ -1162,6 +1163,7 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
if (fields & STAT_CLI_BPS_DOWN){rep["fields"].append("downbps");} if (fields & STAT_CLI_BPS_DOWN){rep["fields"].append("downbps");}
if (fields & STAT_CLI_BPS_UP){rep["fields"].append("upbps");} if (fields & STAT_CLI_BPS_UP){rep["fields"].append("upbps");}
if (fields & STAT_CLI_CRC){rep["fields"].append("crc");} if (fields & STAT_CLI_CRC){rep["fields"].append("crc");}
if (fields & STAT_CLI_SESSID){rep["fields"].append("sessid");}
// output the data itself // output the data itself
rep["data"].null(); rep["data"].null();
// loop over all sessions // loop over all sessions
@ -1185,6 +1187,7 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
if (fields & STAT_CLI_BPS_DOWN){d.append(it->second.getBpsDown(time));} if (fields & STAT_CLI_BPS_DOWN){d.append(it->second.getBpsDown(time));}
if (fields & STAT_CLI_BPS_UP){d.append(it->second.getBpsUp(time));} if (fields & STAT_CLI_BPS_UP){d.append(it->second.getBpsUp(time));}
if (fields & STAT_CLI_CRC){d.append(it->first.crc);} if (fields & STAT_CLI_CRC){d.append(it->first.crc);}
if (fields & STAT_CLI_SESSID){d.append(it->first.ID);}
rep["data"].append(d); rep["data"].append(d);
} }
} }
@ -1235,8 +1238,8 @@ void Controller::fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow){
// collect the data first // collect the data first
std::set<std::string> streams; std::set<std::string> streams;
std::map<std::string, uint64_t> clients; std::map<std::string, uint64_t> clients;
unsigned int tOut = Util::epoch() - STATS_DELAY; uint64_t tOut = Util::bootSecs() - STATS_DELAY;
unsigned int tIn = Util::epoch() - STATS_INPUT_DELAY; uint64_t tIn = Util::bootSecs() - STATS_INPUT_DELAY;
// check all sessions // check all sessions
{ {
tthread::lock_guard<tthread::mutex> guard(statsMutex); tthread::lock_guard<tthread::mutex> guard(statsMutex);
@ -1263,26 +1266,14 @@ void Controller::fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow){
jsonForEach(req, j){ jsonForEach(req, j){
if (j->asStringRef() == "clients"){rep[*it].append(clients[*it]);} if (j->asStringRef() == "clients"){rep[*it].append(clients[*it]);}
if (j->asStringRef() == "lastms"){ if (j->asStringRef() == "lastms"){
char pageId[NAME_BUFFER_SIZE]; DTSC::Meta M(*it, false);
IPC::sharedPage streamIndex; if (M){
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, it->c_str());
streamIndex.init(pageId, DEFAULT_STRM_PAGE_SIZE, false, false);
if (streamIndex.mapped){
static char liveSemName[NAME_BUFFER_SIZE];
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, it->c_str());
IPC::semaphore metaLocker(liveSemName, O_CREAT | O_RDWR, (S_IRWXU | S_IRWXG | S_IRWXO), 8);
metaLocker.wait();
DTSC::Scan strm = DTSC::Packet(streamIndex.mapped, streamIndex.len, true).getScan();
uint64_t lms = 0; uint64_t lms = 0;
DTSC::Scan trcks = strm.getMember("tracks"); std::set<size_t> validTracks = M.getValidTracks();
unsigned int trcks_ctr = trcks.getSize(); for (std::set<size_t>::iterator jt = validTracks.begin(); jt != validTracks.end(); jt++){
for (unsigned int i = 0; i < trcks_ctr; ++i){ if (M.getLastms(*jt) > lms){lms = M.getLastms(*jt);}
if (trcks.getIndice(i).getMember("lastms").asInt() > lms){
lms = trcks.getIndice(i).getMember("lastms").asInt();
}
} }
rep[*it].append(lms); rep[*it].append(lms);
metaLocker.post();
}else{ }else{
rep[*it].append(-1); rep[*it].append(-1);
} }
@ -1330,9 +1321,9 @@ void Controller::fillTotals(JSON::Value &req, JSON::Value &rep){
if (req.isMember("start")){reqStart = req["start"].asInt();} if (req.isMember("start")){reqStart = req["start"].asInt();}
if (req.isMember("end")){reqEnd = req["end"].asInt();} if (req.isMember("end")){reqEnd = req["end"].asInt();}
// add the current time, if negative or zero. // add the current time, if negative or zero.
if (reqStart < 0){reqStart += Util::epoch();} if (reqStart < 0){reqStart += Util::bootSecs();}
if (reqStart == 0){reqStart = Util::epoch() - STAT_CUTOFF;} if (reqStart == 0){reqStart = Util::bootSecs() - STAT_CUTOFF;}
if (reqEnd <= 0){reqEnd += Util::epoch();} if (reqEnd <= 0){reqEnd += Util::bootSecs();}
// at this point, reqStart and reqEnd are the absolute timestamp. // at this point, reqStart and reqEnd are the absolute timestamp.
unsigned int fields = 0; unsigned int fields = 0;
@ -1458,7 +1449,7 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
unsigned long long c_user, c_nice, c_syst, c_idle, c_total; unsigned long long c_user, c_nice, c_syst, c_idle, c_total;
if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){ if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){
c_total = c_user + c_nice + c_syst + c_idle; c_total = c_user + c_nice + c_syst + c_idle;
if (c_total - cl_total > 0){ if (c_total > cl_total){
cpu_use = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total)); cpu_use = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
}else{ }else{
cpu_use = 0; cpu_use = 0;
@ -1556,8 +1547,8 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
// collect the data first // collect the data first
std::map<std::string, uint32_t> outputs; std::map<std::string, uint32_t> outputs;
unsigned long totViewers = 0, totInputs = 0, totOutputs = 0; unsigned long totViewers = 0, totInputs = 0, totOutputs = 0;
unsigned int tOut = Util::epoch() - STATS_DELAY; unsigned int tOut = Util::bootSecs() - STATS_DELAY;
unsigned int tIn = Util::epoch() - STATS_INPUT_DELAY; unsigned int tIn = Util::bootSecs() - STATS_INPUT_DELAY;
// check all sessions // check all sessions
if (sessions.size()){ if (sessions.size()){
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){ for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
@ -1629,8 +1620,7 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
<< it->second.currOuts << "\n"; << it->second.currOuts << "\n";
response << "mist_viewcount{stream=\"" << it->first << "\"}" << it->second.viewers << "\n"; response << "mist_viewcount{stream=\"" << it->first << "\"}" << it->second.viewers << "\n";
response << "mist_bw{stream=\"" << it->first << "\",direction=\"up\"}" << it->second.upBytes << "\n"; response << "mist_bw{stream=\"" << it->first << "\",direction=\"up\"}" << it->second.upBytes << "\n";
response << "mist_bw{stream=\"" << it->first << "\",direction=\"down\"}" response << "mist_bw{stream=\"" << it->first << "\",direction=\"down\"}" << it->second.downBytes << "\n";
<< it->second.downBytes << "\n";
} }
} }
H.Chunkify(response.str(), conn); H.Chunkify(response.str(), conn);
@ -1657,8 +1647,8 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
// collect the data first // collect the data first
std::map<std::string, uint32_t> outputs; std::map<std::string, uint32_t> outputs;
uint64_t totViewers = 0, totInputs = 0, totOutputs = 0; uint64_t totViewers = 0, totInputs = 0, totOutputs = 0;
uint64_t tOut = Util::epoch() - STATS_DELAY; uint64_t tOut = Util::bootSecs() - STATS_DELAY;
uint64_t tIn = Util::epoch() - STATS_INPUT_DELAY; uint64_t tIn = Util::bootSecs() - STATS_INPUT_DELAY;
// check all sessions // check all sessions
if (sessions.size()){ if (sessions.size()){
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){ for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <map> #include <map>
#include <mist/comms.h>
#include <mist/defines.h> #include <mist/defines.h>
#include <mist/http_parser.h> #include <mist/http_parser.h>
#include <mist/json.h> #include <mist/json.h>
@ -37,9 +38,8 @@ namespace Controller{
/// Whenever two of these objects are not equal, it will create a new session. /// Whenever two of these objects are not equal, it will create a new session.
class sessIndex{ class sessIndex{
public: public:
sessIndex(std::string host, unsigned int crc, std::string streamName, std::string connector);
sessIndex(IPC::statExchange &data);
sessIndex(); sessIndex();
sessIndex(const Comms::Statistics &statComm, size_t id);
std::string ID; std::string ID;
std::string host; std::string host;
unsigned int crc; unsigned int crc;
@ -57,7 +57,7 @@ namespace Controller{
class statStorage{ class statStorage{
public: public:
void update(IPC::statExchange &data); void update(Comms::Statistics &statComm, size_t index);
bool hasDataFor(unsigned long long); bool hasDataFor(unsigned long long);
statLog &getDataFor(unsigned long long); statLog &getDataFor(unsigned long long);
std::map<unsigned long long, statLog> log; std::map<unsigned long long, statLog> log;
@ -87,12 +87,13 @@ namespace Controller{
void wipeOld(uint64_t); void wipeOld(uint64_t);
void finish(uint64_t index); void finish(uint64_t index);
void switchOverTo(statSession &newSess, uint64_t index); void switchOverTo(statSession &newSess, uint64_t index);
void update(uint64_t index, IPC::statExchange &data); void update(uint64_t index, Comms::Statistics &data);
void ping(const sessIndex &index, uint64_t disconnectPoint); void dropSession(const sessIndex &index);
uint64_t getStart(); uint64_t getStart();
uint64_t getEnd(); uint64_t getEnd();
bool isViewerOn(uint64_t time); bool isViewerOn(uint64_t time);
bool isViewer(); bool isConnected();
bool isTracked();
bool hasDataFor(uint64_t time); bool hasDataFor(uint64_t time);
bool hasData(); bool hasData();
uint64_t getConnTime(uint64_t time); uint64_t getConnTime(uint64_t time);
@ -110,6 +111,7 @@ namespace Controller{
extern std::map<sessIndex, statSession> sessions; extern std::map<sessIndex, statSession> sessions;
extern std::map<unsigned long, sessIndex> connToSession; extern std::map<unsigned long, sessIndex> connToSession;
extern tthread::mutex statsMutex; extern tthread::mutex statsMutex;
extern uint64_t statDropoff;
struct triggerLog{ struct triggerLog{
uint64_t totalCount; uint64_t totalCount;
@ -119,8 +121,12 @@ namespace Controller{
extern std::map<std::string, triggerLog> triggerStats; extern std::map<std::string, triggerLog> triggerStats;
void statLeadIn();
void statOnActive(size_t id);
void statOnDisconnect(size_t id);
void statLeadOut();
std::set<std::string> getActiveStreams(const std::string &prefix = ""); std::set<std::string> getActiveStreams(const std::string &prefix = "");
void parseStatistics(char *data, size_t len, unsigned int id);
void killStatistics(char *data, size_t len, unsigned int id); void killStatistics(char *data, size_t len, unsigned int id);
void fillClients(JSON::Value &req, JSON::Value &rep); void fillClients(JSON::Value &req, JSON::Value &rep);
void fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow = false); void fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow = false);

View file

@ -139,7 +139,11 @@ namespace Controller{
void initState(){ void initState(){
tthread::lock_guard<tthread::mutex> guard(logMutex); tthread::lock_guard<tthread::mutex> guard(logMutex);
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, true); // max 1M of logs cached shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, false, false); // max 1M of logs cached
if (!shmLogs || !shmLogs->mapped){
if (shmLogs){delete shmLogs;}
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, true); // max 1M of logs cached
}
if (!shmLogs->mapped){ if (!shmLogs->mapped){
FAIL_MSG("Could not open memory page for logs buffer"); FAIL_MSG("Could not open memory page for logs buffer");
return; return;
@ -156,7 +160,11 @@ namespace Controller{
} }
maxLogsRecs = (1024 * 1024 - rlxLogs->getOffset()) / rlxLogs->getRSize(); maxLogsRecs = (1024 * 1024 - rlxLogs->getOffset()) / rlxLogs->getRSize();
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, true); // max 1M of accesslogs cached shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, false, false); // max 1M of accesslogs cached
if (!shmAccs || !shmAccs->mapped){
if (shmAccs){delete shmAccs;}
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, true); // max 1M of accesslogs cached
}
if (!shmAccs->mapped){ if (!shmAccs->mapped){
FAIL_MSG("Could not open memory page for access logs buffer"); FAIL_MSG("Could not open memory page for access logs buffer");
return; return;
@ -176,7 +184,11 @@ namespace Controller{
} }
maxAccsRecs = (1024 * 1024 - rlxAccs->getOffset()) / rlxAccs->getRSize(); maxAccsRecs = (1024 * 1024 - rlxAccs->getOffset()) / rlxAccs->getRSize();
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, true); // max 1M of stream data shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, false, false); // max 1M of stream data
if (!shmStrm || !shmStrm->mapped){
if (shmStrm){delete shmStrm;}
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, true); // max 1M of stream data
}
if (!shmStrm->mapped){ if (!shmStrm->mapped){
FAIL_MSG("Could not open memory page for stream data"); FAIL_MSG("Could not open memory page for stream data");
return; return;

View file

@ -295,8 +295,8 @@ namespace Controller{
Util::sanitizeName(cleaned); Util::sanitizeName(cleaned);
std::string strmSource; std::string strmSource;
if (Util::getStreamStatus(cleaned) != STRMSTAT_OFF){ if (Util::getStreamStatus(cleaned) != STRMSTAT_OFF){
DTSC::Meta mData = Util::getStreamMeta(cleaned); DTSC::Meta M(cleaned, false);
if (mData.sourceURI.size()){strmSource = mData.sourceURI;} if (M && M.getSource().size()){strmSource = M.getSource();}
} }
if (!strmSource.size()){ if (!strmSource.size()){
std::string smp = cleaned.substr(0, cleaned.find_first_of("+ ")); std::string smp = cleaned.substr(0, cleaned.find_first_of("+ "));

View file

@ -24,30 +24,6 @@
#define SHARED_SECRET "empty" #define SHARED_SECRET "empty"
#endif #endif
static std::string readFile(std::string filename){
std::ifstream file(filename.c_str());
if (!file.good()){return "";}
file.seekg(0, std::ios::end);
unsigned int len = file.tellg();
file.seekg(0, std::ios::beg);
std::string out;
out.reserve(len);
unsigned int i = 0;
while (file.good() && i++ < len){out += file.get();}
file.close();
return out;
}
static bool writeFile(std::string filename, std::string &contents){
unlink(filename.c_str());
std::ofstream file(filename.c_str(), std::ios_base::trunc | std::ios_base::out);
if (!file.is_open()){return false;}
file << contents;
file.close();
chmod(filename.c_str(), S_IRWXU | S_IRWXG);
return true;
}
tthread::mutex updaterMutex; tthread::mutex updaterMutex;
uint8_t updatePerc = 0; uint8_t updatePerc = 0;
JSON::Value updates; JSON::Value updates;

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,11 @@
#include <cstdlib> #include <cstdlib>
#include <fstream> #include <fstream>
#include <map> #include <map>
#include <mist/bitfields.h>
#include <mist/config.h> #include <mist/config.h>
#include <mist/defines.h> #include <mist/defines.h>
#include <mist/dtsc.h> #include <mist/dtsc.h>
#include <mist/encryption.h>
#include <mist/json.h> #include <mist/json.h>
#include <mist/shared_memory.h> #include <mist/shared_memory.h>
#include <mist/timing.h> #include <mist/timing.h>
@ -13,9 +15,9 @@
namespace Mist{ namespace Mist{
struct booking{ struct booking{
int first; uint32_t first;
int curKey; uint32_t curKey;
int curPart; uint32_t curPart;
}; };
class Input : public InOutBase{ class Input : public InOutBase{
@ -26,61 +28,63 @@ namespace Mist{
virtual int boot(int argc, char *argv[]); virtual int boot(int argc, char *argv[]);
virtual ~Input(){}; virtual ~Input(){};
bool keepAlive();
void reloadClientMeta();
bool hasMeta() const;
static Util::Config *config; static Util::Config *config;
virtual bool needsLock(){return !config->getBool("realtime");} virtual bool needsLock(){return !config->getBool("realtime");}
protected: protected:
static void callbackWrapper(char *data, size_t len, unsigned int id);
virtual bool checkArguments() = 0; virtual bool checkArguments() = 0;
virtual bool readHeader() = 0; virtual bool readHeader();
virtual bool needHeader(){return !readExistingHeader();} virtual bool needHeader(){return !readExistingHeader();}
virtual bool preRun(){return true;} virtual bool preRun(){return true;}
virtual bool isSingular(){return !config->getBool("realtime");} virtual bool isSingular(){return !config->getBool("realtime");}
virtual bool readExistingHeader(); virtual bool readExistingHeader();
virtual bool atKeyFrame(); virtual bool atKeyFrame();
virtual void getNext(bool smart = true){} virtual void getNext(size_t idx = INVALID_TRACK_ID){}
virtual void seek(int seekTime){}; virtual void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){}
virtual void finish(); virtual void finish();
virtual bool keepRunning(); virtual bool keepRunning();
virtual bool openStreamSource(){return readHeader();} virtual bool openStreamSource(){return readHeader();}
virtual void closeStreamSource(){} virtual void closeStreamSource(){}
virtual void parseStreamHeader(){} virtual void parseStreamHeader(){}
void play(int until = 0);
void playOnce();
void quitPlay();
void checkHeaderTimes(std::string streamFile); void checkHeaderTimes(std::string streamFile);
virtual void removeUnused(); virtual void removeUnused();
virtual void trackSelect(std::string trackSpec);
virtual void userCallback(char *data, size_t len, unsigned int id);
virtual void convert(); virtual void convert();
virtual void serve(); virtual void serve();
virtual void stream(); virtual void stream();
virtual size_t streamByteCount(){
return 0;
}; // For live streams: to update the stats with correct values.
virtual std::string streamMainLoop(); virtual std::string streamMainLoop();
virtual std::string realtimeMainLoop(); virtual std::string realtimeMainLoop();
bool isAlwaysOn(); bool isAlwaysOn();
virtual void userLeadIn();
virtual void userOnActive(size_t id);
virtual void userOnDisconnect(size_t id);
virtual void userLeadOut();
virtual void parseHeader(); virtual void parseHeader();
bool bufferFrame(unsigned int track, unsigned int keyNum); bool bufferFrame(size_t track, uint32_t keyNum);
unsigned int packTime; /// Media-timestamp of the last packet.
int lastActive; /// Timestamp of the last time we received or sent something.
int initialTime;
int playing;
unsigned int playUntil;
bool isBuffer;
uint64_t activityCounter; uint64_t activityCounter;
JSON::Value capa; JSON::Value capa;
std::map<int, std::set<int> > keyTimes;
int64_t timeOffset; int64_t timeOffset;
std::map<size_t, std::set<uint64_t> > keyTimes;
// Create server for user pages // Create server for user pages
IPC::sharedServer userPage; Comms::Users users;
size_t connectedUsers;
Encryption::AES aesCipher;
IPC::sharedPage streamStatus; IPC::sharedPage streamStatus;
std::map<unsigned int, std::map<unsigned int, unsigned int> > pageCounter; std::map<size_t, std::map<uint32_t, size_t> > pageCounter;
static Input *singleton; static Input *singleton;
@ -93,6 +97,7 @@ namespace Mist{
DTSC::Packet srtPack; DTSC::Packet srtPack;
uint64_t simStartTime; uint64_t simStartTime;
};
void handleBuyDRM();
};
}// namespace Mist }// namespace Mist

View file

@ -72,7 +72,7 @@ namespace Mist{
if (ret != 0){ if (ret != 0){
char errstr[300]; char errstr[300];
av_strerror(ret, errstr, 300); av_strerror(ret, errstr, 300);
DEBUG_MSG(DLVL_FAIL, "Could not open file: %s", errstr); FAIL_MSG("Could not open file: %s", errstr);
return false; // Couldn't open file return false; // Couldn't open file
} }
@ -81,7 +81,7 @@ namespace Mist{
if (ret < 0){ if (ret < 0){
char errstr[300]; char errstr[300];
av_strerror(ret, errstr, 300); av_strerror(ret, errstr, 300);
DEBUG_MSG(DLVL_FAIL, "Could not find stream info: %s", errstr); FAIL_MSG("Could not find stream info: %s", errstr);
return false; return false;
} }
return true; return true;
@ -160,12 +160,12 @@ namespace Mist{
return true; return true;
} }
void inputAV::getNext(bool smart){ void inputAV::getNext(){
AVPacket packet; AVPacket packet;
while (av_read_frame(pFormatCtx, &packet) >= 0){ while (av_read_frame(pFormatCtx, &packet) >= 0){
// filter tracks we don't care about // filter tracks we don't care about
if (!selectedTracks.count(packet.stream_index + 1)){ if (!selectedTracks.count(packet.stream_index + 1)){
DEBUG_MSG(DLVL_HIGH, "Track %u not selected", packet.stream_index + 1); HIGH_MSG("Track %u not selected", packet.stream_index + 1);
continue; continue;
} }
AVStream *strm = pFormatCtx->streams[packet.stream_index]; AVStream *strm = pFormatCtx->streams[packet.stream_index];
@ -187,7 +187,7 @@ namespace Mist{
thisPacket.null(); thisPacket.null();
preRun(); preRun();
// failure :-( // failure :-(
DEBUG_MSG(DLVL_FAIL, "getNext failed"); FAIL_MSG("getNext failed");
} }
void inputAV::seek(int seekTime){ void inputAV::seek(int seekTime){

View file

@ -23,7 +23,7 @@ namespace Mist{
bool checkArguments(); bool checkArguments();
bool preRun(); bool preRun();
bool readHeader(); bool readHeader();
void getNext(bool smart = true); void getNext();
void seek(int seekTime); void seek(int seekTime);
void trackSelect(std::string trackSpec); void trackSelect(std::string trackSpec);

View file

@ -47,7 +47,7 @@ namespace Mist{
Socket::Connection balConn(url.host, url.getPort(), true); Socket::Connection balConn(url.host, url.getPort(), true);
if (!balConn){ if (!balConn){
WARN_MSG("Failed to reach %s on port %lu", url.host.c_str(), url.getPort()); WARN_MSG("Failed to reach %s on port %" PRIu16, url.host.c_str(), url.getPort());
}else{ }else{
HTTP::Parser http; HTTP::Parser http;
http.url = "/" + url.path; http.url = "/" + url.path;

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
#include <fstream>
#include "input.h" #include "input.h"
#include <fstream>
#include <mist/dtsc.h> #include <mist/dtsc.h>
#include <mist/shared_memory.h> #include <mist/shared_memory.h>
@ -12,11 +11,12 @@ namespace Mist{
void onCrash(); void onCrash();
private: private:
void fillBufferDetails(JSON::Value &details); void fillBufferDetails(JSON::Value &details) const;
unsigned int bufferTime; uint64_t bufferTime;
unsigned int cutTime; uint64_t cutTime;
unsigned int segmentSize; /*LTS*/ size_t segmentSize; /*LTS*/
unsigned int lastReTime; /*LTS*/ uint64_t lastReTime; /*LTS*/
uint64_t finalMillis;
bool hasPush; bool hasPush;
bool resumeMode; bool resumeMode;
IPC::semaphore *liveMeta; IPC::semaphore *liveMeta;
@ -28,29 +28,30 @@ namespace Mist{
void updateMeta(); void updateMeta();
bool readHeader(){return false;} bool readHeader(){return false;}
bool needHeader(){return false;} bool needHeader(){return false;}
void getNext(bool smart = true){} void getNext(size_t idx = INVALID_TRACK_ID){};
void updateTrackMeta(unsigned long tNum); void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){};
void updateMetaFromPage(unsigned long tNum, unsigned long pageNum);
void seek(int seekTime){} void removeTrack(size_t tid);
void trackSelect(std::string trackSpec){}
bool removeKey(unsigned int tid); bool removeKey(size_t tid);
void removeUnused(); void removeUnused();
void eraseTrackDataPages(unsigned long tid);
void finish(); void finish();
void userCallback(char *data, size_t len, unsigned int id);
std::set<unsigned long> negotiatingTracks; uint64_t retrieveSetting(DTSC::Scan &streamCfg, const std::string &setting, const std::string &option = "");
std::set<unsigned long> activeTracks;
std::map<unsigned long, unsigned long long> lastUpdated; void userLeadIn();
std::map<unsigned long, unsigned long long> negotiationTimeout; void userOnActive(size_t id);
/// Maps trackid to a pagenum->pageData map void userOnDisconnect(size_t id);
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations; void userLeadOut();
std::map<unsigned long, char *> pushLocation;
inputBuffer *singleton;
// This is used for an ugly fix to prevent metadata from disappearing in some cases. // This is used for an ugly fix to prevent metadata from disappearing in some cases.
std::map<unsigned long, std::string> initData; std::map<size_t, std::string> initData;
uint64_t findTrack(const std::string &trackVal); uint64_t findTrack(const std::string &trackVal);
void checkProcesses(const JSON::Value &procs); // LTS void checkProcesses(const JSON::Value &procs); // LTS
std::map<std::string, pid_t> runningProcs; // LTS std::map<std::string, pid_t> runningProcs; // LTS
std::set<size_t> generatePids;
std::map<size_t, std::set<size_t> > sourcePids;
}; };
}// namespace Mist }// namespace Mist

View file

@ -60,10 +60,19 @@ namespace Mist{
capa["optional"]["segmentsize"]["type"] = "uint"; capa["optional"]["segmentsize"]["type"] = "uint";
capa["optional"]["segmentsize"]["default"] = 1900; capa["optional"]["segmentsize"]["default"] = 1900;
/*LTS-END*/ /*LTS-END*/
F = NULL;
lockCache = false;
lockNeeded = false;
} }
bool inputDTSC::needsLock(){ bool inputDTSC::needsLock(){
return config->getString("input").substr(0, 7) != "dtsc://" && config->getString("input") != "-"; if (!lockCache){
lockNeeded =
config->getString("input").substr(0, 7) != "dtsc://" && config->getString("input") != "-";
lockCache = true;
}
return lockNeeded;
} }
void parseDTSCURI(const std::string &src, std::string &host, uint16_t &port, void parseDTSCURI(const std::string &src, std::string &host, uint16_t &port,
@ -129,37 +138,40 @@ namespace Mist{
void inputDTSC::parseStreamHeader(){ void inputDTSC::parseStreamHeader(){
while (srcConn.connected() && config->is_active){ while (srcConn.connected() && config->is_active){
srcConn.spool(); srcConn.spool();
if (srcConn.Received().available(8)){ if (!srcConn.Received().available(8)){
if (srcConn.Received().copy(4) == "DTCM" || srcConn.Received().copy(4) == "DTSC"){
// Command message
std::string toRec = srcConn.Received().copy(8);
unsigned long rSize = Bit::btohl(toRec.c_str() + 4);
if (!srcConn.Received().available(8 + rSize)){
nProxy.userClient.keepAlive();
Util::sleep(100);
continue; // abort - not enough data yet
}
// Ignore initial DTCM message, as this is a "hi" message from the server
if (srcConn.Received().copy(4) == "DTCM"){
srcConn.Received().remove(8 + rSize);
}else{
std::string dataPacket = srcConn.Received().remove(8 + rSize);
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
myMeta.reinit(metaPack);
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
continueNegotiate(it->first, true);
}
break;
}
}else{
INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str());
break;
}
}else{
Util::sleep(100); Util::sleep(100);
nProxy.userClient.keepAlive(); keepAlive();
continue;
} }
if (srcConn.Received().copy(4) != "DTCM" && srcConn.Received().copy(4) != "DTSC"){
INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str());
break;
}
// Command message
std::string toRec = srcConn.Received().copy(8);
uint32_t rSize = Bit::btohl(toRec.c_str() + 4);
if (!srcConn.Received().available(8 + rSize)){
keepAlive();
Util::sleep(100);
continue; // abort - not enough data yet
}
// Ignore initial DTCM message, as this is a "hi" message from the server
if (srcConn.Received().copy(4) == "DTCM"){
srcConn.Received().remove(8 + rSize);
continue;
}
std::string dataPacket = srcConn.Received().remove(8 + rSize);
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
DTSC::Meta nM("", metaPack.getScan());
meta.reInit(streamName, false);
meta.merge(nM);
std::set<size_t> validTracks = M.getMySourceTracks(getpid());
userSelect.clear();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
userSelect[*it].reload(streamName, *it, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
}
break;
} }
} }
@ -194,25 +206,26 @@ namespace Mist{
void inputDTSC::closeStreamSource(){srcConn.close();} void inputDTSC::closeStreamSource(){srcConn.close();}
bool inputDTSC::checkArguments(){ bool inputDTSC::checkArguments(){
if (!needsLock()){ if (!needsLock()){return true;}
return true; if (!config->getString("streamname").size()){
}else{ if (config->getString("output") == "-"){
if (!config->getString("streamname").size()){ std::cerr << "Output to stdout not yet supported" << std::endl;
if (config->getString("output") == "-"){ return false;
std::cerr << "Output to stdout not yet supported" << std::endl; }
return false; }else{
} if (config->getString("output") != "-"){
}else{ std::cerr << "File output in player mode not supported" << std::endl;
if (config->getString("output") != "-"){ return false;
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
} }
// open File
inFile = DTSC::File(config->getString("input"));
if (!inFile){return false;}
} }
// open File
F = fopen(config->getString("input").c_str(), "r+b");
if (!F){
HIGH_MSG("Could not open file %s", config->getString("input").c_str());
return false;
}
fseek(F, 0, SEEK_SET);
return true; return true;
} }
@ -222,120 +235,215 @@ namespace Mist{
} }
bool inputDTSC::readHeader(){ bool inputDTSC::readHeader(){
if (!inFile){return false;} if (!F){return false;}
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0){ if (!readExistingHeader()){
DEBUG_MSG(DLVL_FAIL, "Missing external header file"); size_t moreHeader = 0;
return false; do{
// read existing header from file here?
char hdr[8];
fseek(F, moreHeader, SEEK_SET);
if (fread(hdr, 8, 1, F) != 1){
FAIL_MSG("Could not read header @ bpos %zu", moreHeader);
return false;
}
if (memcmp(hdr, DTSC::Magic_Header, 4)){
FAIL_MSG("File does not have a DTSC header @ bpos %zu", moreHeader);
return false;
}
size_t pktLen = Bit::btohl(hdr + 4);
char *pkt = (char *)malloc(8 + pktLen * sizeof(char));
fseek(F, moreHeader, SEEK_SET);
if (fread(pkt, 8 + pktLen, 1, F) != 1){
free(pkt);
FAIL_MSG("Could not read packet @ bpos %zu", moreHeader);
}
DTSC::Scan S(pkt + 8, pktLen);
if (S.hasMember("moreheader") && S.getMember("moreheader").asInt()){
moreHeader = S.getMember("moreheader").asInt();
}else{
moreHeader = 0;
meta.reInit(streamName, moreHeader);
}
free(pkt);
}while (moreHeader);
} }
myMeta = DTSC::Meta(inFile.getMeta());
DEBUG_MSG(DLVL_DEVEL, "Meta read in with %lu tracks", myMeta.tracks.size()); return meta;
return true;
} }
void inputDTSC::getNext(bool smart){ void inputDTSC::getNext(size_t idx){
if (!needsLock()){ if (!needsLock()){
thisPacket.reInit(srcConn); getNextFromStream(idx);
while (config->is_active){ return;
if (thisPacket.getVersion() == DTSC::DTCM){ }
nProxy.userClient.keepAlive(); if (!currentPositions.size()){
std::string cmd; WARN_MSG("No seek positions set - returning empty packet.");
thisPacket.getString("cmd", cmd); thisPacket.null();
if (cmd == "reset"){ return;
// Read next packet }
thisPacket.reInit(srcConn); seekPos thisPos = *currentPositions.begin();
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){ fseek(F, thisPos.bytePos, SEEK_SET);
DTSC::Meta newMeta; if (feof(F)){
newMeta.reinit(thisPacket); thisPacket.null();
// Detect new tracks return;
std::set<unsigned int> newTracks; }
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin(); clearerr(F);
it != newMeta.tracks.end(); it++){ currentPositions.erase(currentPositions.begin());
if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);} lastreadpos = ftell(F);
} if (fread(buffer, 4, 1, F) != 1){
if (feof(F)){
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){ INFO_MSG("End of file reached while seeking @ %" PRIu64, lastreadpos);
INFO_MSG("Reset: adding track %d", *it); }else{
myMeta.tracks[*it] = newMeta.tracks[*it]; ERROR_MSG("Could not seek to next @ %" PRIu64, lastreadpos);
continueNegotiate(*it, true);
}
// Detect removed tracks
std::set<unsigned int> deletedTracks;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
if (!newMeta.tracks.count(it->first)){deletedTracks.insert(it->first);}
}
for (std::set<unsigned int>::iterator it = deletedTracks.begin();
it != deletedTracks.end(); it++){
INFO_MSG("Reset: deleting track %d", *it);
myMeta.tracks.erase(*it);
}
thisPacket.reInit(srcConn); // read the next packet before continuing
}else{
myMeta = DTSC::Meta();
}
}else{
thisPacket.reInit(srcConn); // read the next packet before continuing
}
continue; // parse the next packet before returning
}else if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
DTSC::Meta newMeta;
newMeta.reinit(thisPacket);
std::set<unsigned int> newTracks;
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin();
it != newMeta.tracks.end(); it++){
if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);}
}
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){
INFO_MSG("New header: adding track %d (%s)", *it, newMeta.tracks[*it].type.c_str());
myMeta.tracks[*it] = newMeta.tracks[*it];
continueNegotiate(*it, true);
}
thisPacket.reInit(srcConn); // read the next packet before continuing
continue; // parse the next packet before returning
}
// We now know we have either a data packet, or an error.
if (!thisPacket.getTrackId()){
if (thisPacket.getVersion() == DTSC::DTSC_V2){
WARN_MSG("Received bad packet for stream %s: %llu@%llu", streamName.c_str(),
thisPacket.getTrackId(), thisPacket.getTime());
}else{
// All types except data packets are handled above, so if it's not a V2 data packet, we assume corruption
WARN_MSG("Invalid packet header for stream %s", streamName.c_str());
}
}
return; // we have a packet
} }
thisPacket.null();
return;
}
if (memcmp(buffer, DTSC::Magic_Header, 4) == 0){
seekNext(thisPacket.getTime(), thisPacket.getTrackId(), true);
getNext(idx);
return;
}
uint8_t version = 0;
if (memcmp(buffer, DTSC::Magic_Packet, 4) == 0){version = 1;}
if (memcmp(buffer, DTSC::Magic_Packet2, 4) == 0){version = 2;}
if (version == 0){
ERROR_MSG("Invalid packet header @ %#" PRIx64 " - %.4s != %.4s @ %" PRIu64, lastreadpos,
buffer, DTSC::Magic_Packet2, lastreadpos);
thisPacket.null();
return;
}
if (fread(buffer + 4, 4, 1, F) != 1){
ERROR_MSG("Could not read packet size @ %" PRIu64, lastreadpos);
thisPacket.null();
return;
}
std::string pBuf;
uint32_t packSize = Bit::btohl(buffer + 4);
pBuf.resize(8 + packSize);
memcpy((char *)pBuf.data(), buffer, 8);
if (fread((void *)(pBuf.data() + 8), packSize, 1, F) != 1){
ERROR_MSG("Could not read packet @ %" PRIu64, lastreadpos);
thisPacket.null();
return;
}
thisPacket.reInit(pBuf.data(), pBuf.size());
seekNext(thisPos.seekTime, thisPos.trackID);
fseek(F, thisPos.bytePos, SEEK_SET);
}
void inputDTSC::getNextFromStream(size_t idx){
thisPacket.reInit(srcConn);
while (config->is_active){
if (thisPacket.getVersion() == DTSC::DTCM){
// userClient.keepAlive();
std::string cmd;
thisPacket.getString("cmd", cmd);
if (cmd != "reset"){
thisPacket.reInit(srcConn);
continue;
}
// Read next packet
thisPacket.reInit(srcConn);
if (thisPacket.getVersion() != DTSC::DTSC_HEAD){
meta.clear();
continue;
}
DTSC::Meta nM("", thisPacket.getScan());
meta.merge(nM, true, false);
thisPacket.reInit(srcConn); // read the next packet before continuing
continue; // parse the next packet before returning
}
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
DTSC::Meta nM("", thisPacket.getScan());
meta.merge(nM, false, false);
thisPacket.reInit(srcConn); // read the next packet before continuing
continue; // parse the next packet before returning
}
thisPacket = DTSC::Packet(thisPacket, M.trackIDToIndex(thisPacket.getTrackId(), getpid()));
return; // we have a packet
}
}
void inputDTSC::seek(uint64_t seekTime, size_t idx){
currentPositions.clear();
if (idx != INVALID_TRACK_ID){
seekNext(seekTime, idx, true);
}else{ }else{
if (smart){ std::set<size_t> tracks = M.getValidTracks();
inFile.seekNext(); for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
}else{ seekNext(seekTime, *it, true);
inFile.parseNext();
} }
thisPacket = inFile.getPacket();
} }
} }
void inputDTSC::seek(int seekTime){ void inputDTSC::seekNext(uint64_t ms, size_t trackIdx, bool forceSeek){
inFile.seek_time(seekTime); seekPos tmpPos;
initialTime = 0; tmpPos.trackID = trackIdx;
playUntil = 0; if (!forceSeek && thisPacket && ms >= thisPacket.getTime() && trackIdx >= thisPacket.getTrackId()){
} tmpPos.seekTime = thisPacket.getTime();
tmpPos.bytePos = ftell(F);
void inputDTSC::trackSelect(std::string trackSpec){ }else{
selectedTracks.clear(); tmpPos.seekTime = 0;
long long unsigned int index; tmpPos.bytePos = 0;
while (trackSpec != ""){ }
index = trackSpec.find(' '); if (feof(F)){
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str())); clearerr(F);
if (index != std::string::npos){ fseek(F, 0, SEEK_SET);
trackSpec.erase(0, index + 1); tmpPos.bytePos = 0;
tmpPos.seekTime = 0;
}
DTSC::Keys keys(M.keys(trackIdx));
uint32_t keyNum = keys.getNumForTime(ms);
if (keys.getTime(keyNum) > tmpPos.seekTime){
tmpPos.seekTime = keys.getTime(keyNum);
tmpPos.bytePos = keys.getBpos(keyNum);
}
bool foundPacket = false;
while (!foundPacket){
lastreadpos = ftell(F);
if (feof(F)){
WARN_MSG("Reached EOF during seek to %" PRIu64 " in track %zu - aborting @ %" PRIu64, ms,
trackIdx, lastreadpos);
return;
}
// Seek to first packet after ms.
fseek(F, tmpPos.bytePos, SEEK_SET);
lastreadpos = ftell(F);
// read the header
char header[20];
if (fread((void *)header, 20, 1, F) != 1){
WARN_MSG("Could not read header from file. Much sadface.");
return;
}
// check if packetID matches, if not, skip size + 8 bytes.
uint32_t packSize = Bit::btohl(header + 4);
uint32_t packID = Bit::btohl(header + 8);
if (memcmp(header, DTSC::Magic_Packet2, 4) != 0 || packID != trackIdx){
if (memcmp(header, "DT", 2) != 0){
WARN_MSG("Invalid header during seek to %" PRIu64 " in track %zu @ %" PRIu64
" - resetting bytePos from %" PRIu64 " to zero",
ms, trackIdx, lastreadpos, tmpPos.bytePos);
tmpPos.bytePos = 0;
continue;
}
tmpPos.bytePos += 8 + packSize;
continue;
}
// get timestamp of packet, if too large, break, if not, skip size bytes.
uint64_t myTime = Bit::btohll(header + 12);
tmpPos.seekTime = myTime;
if (myTime >= ms){
foundPacket = true;
}else{ }else{
trackSpec = ""; tmpPos.bytePos += 8 + packSize;
continue;
} }
} }
inFile.selectTracks(selectedTracks); // HIGH_MSG("Seek to %u:%d resulted in %lli", trackIdx, ms, tmpPos.seekTime);
if (tmpPos.seekTime > 0xffffffffffffff00ll){tmpPos.seekTime = 0;}
currentPositions.insert(tmpPos);
return;
} }
}// namespace Mist }// namespace Mist

View file

@ -1,7 +1,27 @@
#include "input.h" #include "input.h"
#include <set>
#include <stdio.h> //for FILE
#include <mist/dtsc.h> #include <mist/dtsc.h>
namespace Mist{ namespace Mist{
///\brief A simple structure used for ordering byte seek positions.
struct seekPos{
///\brief Less-than comparison for seekPos structures.
///\param rhs The seekPos to compare with.
///\return Whether this object is smaller than rhs.
bool operator<(const seekPos &rhs) const{
if (seekTime < rhs.seekTime){return true;}
if (seekTime == rhs.seekTime){return trackID < rhs.trackID;}
return false;
}
uint64_t seekTime; ///< Stores the timestamp of the DTSC packet referenced by this structure.
uint64_t bytePos; ///< Stores the byteposition of the DTSC packet referenced by this structure.
uint32_t trackID; ///< Stores the track the DTSC packet referenced by this structure is
///< associated with.
};
class inputDTSC : public Input{ class inputDTSC : public Input{
public: public:
inputDTSC(Util::Config *cfg); inputDTSC(Util::Config *cfg);
@ -15,13 +35,24 @@ namespace Mist{
bool checkArguments(); bool checkArguments();
bool readHeader(); bool readHeader();
bool needHeader(); bool needHeader();
void getNext(bool smart = true); void getNext(size_t idx = INVALID_TRACK_ID);
void seek(int seekTime); void getNextFromStream(size_t idx = INVALID_TRACK_ID);
void trackSelect(std::string trackSpec); void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
DTSC::File inFile; FILE *F;
Socket::Connection srcConn; Socket::Connection srcConn;
bool lockCache;
bool lockNeeded;
std::set<seekPos> currentPositions;
uint64_t lastreadpos;
char buffer[8];
void seekNext(uint64_t ms, size_t trackIdx, bool forceSeek = false);
}; };
}// namespace Mist }// namespace Mist

View file

@ -41,6 +41,7 @@ namespace Mist{
lastClusterTime = 0; lastClusterTime = 0;
bufferedPacks = 0; bufferedPacks = 0;
wantBlocks = true; wantBlocks = true;
totalBytes = 0;
} }
std::string ASStoSRT(const char *ptr, uint32_t len){ std::string ASStoSRT(const char *ptr, uint32_t len){
@ -112,13 +113,15 @@ namespace Mist{
uint32_t needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal); uint32_t needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal);
while (ptr.size() < needed){ while (ptr.size() < needed){
if (!ptr.allocate(needed)){return false;} if (!ptr.allocate(needed)){return false;}
if (!fread(ptr + ptr.size(), needed - ptr.size(), 1, inFile)){ int64_t toRead = needed - ptr.size();
if (!fread(ptr + ptr.size(), toRead, 1, inFile)){
// We assume if there is no current data buffered, that we are at EOF and don't print a warning // We assume if there is no current data buffered, that we are at EOF and don't print a warning
if (ptr.size()){ if (ptr.size()){
FAIL_MSG("Could not read more data! (have %lu, need %lu)", ptr.size(), needed); FAIL_MSG("Could not read more data! (have %zu, need %" PRIu32 ")", ptr.size(), needed);
} }
return false; return false;
} }
totalBytes += toRead;
ptr.size() = needed; ptr.size() = needed;
needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal); needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal);
if (ptr.size() >= needed){ if (ptr.size() >= needed){
@ -141,26 +144,26 @@ namespace Mist{
lastClusterBPos = bp; lastClusterBPos = bp;
} }
} }
DONTEVEN_MSG("Found a cluster at position %llu", lastClusterBPos); DONTEVEN_MSG("Found a cluster at position %" PRIu64, lastClusterBPos);
} }
if (E.getID() == EBML::EID_TIMECODE){ if (E.getID() == EBML::EID_TIMECODE){
lastClusterTime = E.getValUInt(); lastClusterTime = E.getValUInt();
DONTEVEN_MSG("Cluster time %llu ms", lastClusterTime); DONTEVEN_MSG("Cluster time %" PRIu64 " ms", lastClusterTime);
} }
return true; return true;
} }
bool InputEBML::readExistingHeader(){ bool InputEBML::readExistingHeader(){
if (!Input::readExistingHeader()){return false;} if (!Input::readExistingHeader()){return false;}
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); std::set<size_t> validTracks = M.getValidTracks();
it != myMeta.tracks.end(); ++it){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (it->second.codec == "PCMLE"){ if (M.getCodec(*it) == "PCMLE"){
it->second.codec = "PCM"; meta.setCodec(*it, "PCM");
swapEndianness.insert(it->first); swapEndianness.insert(*it);
} }
} }
if (myMeta.inputLocalVars.isMember("timescale")){ if (M.inputLocalVars.isMember("timescale")){
timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0; timeScale = ((double)M.inputLocalVars["timescale"].asInt()) / 1000000.0;
} }
return true; return true;
} }
@ -169,6 +172,7 @@ namespace Mist{
if (!inFile){return false;} if (!inFile){return false;}
// Create header file from file // Create header file from file
uint64_t bench = Util::getMicros(); uint64_t bench = Util::getMicros();
if (!meta){meta.reInit(streamName);}
while (readElement()){ while (readElement()){
EBML::Element E(ptr, readingMinimal); EBML::Element E(ptr, readingMinimal);
@ -178,7 +182,7 @@ namespace Mist{
ERROR_MSG("Track without track number encountered, ignoring"); ERROR_MSG("Track without track number encountered, ignoring");
continue; continue;
} }
uint64_t trackNo = tmpElem.getValUInt(); uint64_t trackID = tmpElem.getValUInt();
tmpElem = E.findChild(EBML::EID_CODECID); tmpElem = E.findChild(EBML::EID_CODECID);
if (!tmpElem){ if (!tmpElem){
ERROR_MSG("Track without codec id encountered, ignoring"); ERROR_MSG("Track without codec id encountered, ignoring");
@ -311,32 +315,33 @@ namespace Mist{
} }
tmpElem = E.findChild(EBML::EID_LANGUAGE); tmpElem = E.findChild(EBML::EID_LANGUAGE);
if (tmpElem){lang = tmpElem.getValString();} if (tmpElem){lang = tmpElem.getValString();}
DTSC::Track &Trk = myMeta.tracks[trackNo]; size_t idx = M.trackIDToIndex(trackID, getpid());
Trk.trackID = trackNo; if (idx == INVALID_TRACK_ID){idx = meta.addTrack();}
Trk.lang = lang; meta.setID(idx, trackID);
Trk.codec = trueCodec; meta.setLang(idx, lang);
Trk.type = trueType; meta.setCodec(idx, trueCodec);
Trk.init = init; meta.setType(idx, trueType);
if (Trk.type == "video"){ meta.setInit(idx, init);
if (trueType == "video"){
tmpElem = E.findChild(EBML::EID_PIXELWIDTH); tmpElem = E.findChild(EBML::EID_PIXELWIDTH);
Trk.width = tmpElem ? tmpElem.getValUInt() : 0; meta.setWidth(idx, tmpElem ? tmpElem.getValUInt() : 0);
tmpElem = E.findChild(EBML::EID_PIXELHEIGHT); tmpElem = E.findChild(EBML::EID_PIXELHEIGHT);
Trk.height = tmpElem ? tmpElem.getValUInt() : 0; meta.setHeight(idx, tmpElem ? tmpElem.getValUInt() : 0);
Trk.fpks = 0; meta.setFpks(idx, 0);
} }
if (Trk.type == "audio"){ if (trueType == "audio"){
tmpElem = E.findChild(EBML::EID_CHANNELS); tmpElem = E.findChild(EBML::EID_CHANNELS);
Trk.channels = tmpElem ? tmpElem.getValUInt() : 1; meta.setChannels(idx, tmpElem ? tmpElem.getValUInt() : 1);
tmpElem = E.findChild(EBML::EID_BITDEPTH); tmpElem = E.findChild(EBML::EID_BITDEPTH);
Trk.size = tmpElem ? tmpElem.getValUInt() : 0; meta.setSize(idx, tmpElem ? tmpElem.getValUInt() : 0);
tmpElem = E.findChild(EBML::EID_SAMPLINGFREQUENCY); tmpElem = E.findChild(EBML::EID_SAMPLINGFREQUENCY);
Trk.rate = tmpElem ? (int)tmpElem.getValFloat() : 8000; meta.setRate(idx, tmpElem ? (int)tmpElem.getValFloat() : 8000);
} }
INFO_MSG("Detected track: %s", Trk.getIdentifier().c_str()); INFO_MSG("Detected track: %s", M.getTrackIdentifier(idx).c_str());
} }
if (E.getID() == EBML::EID_TIMECODESCALE){ if (E.getID() == EBML::EID_TIMECODESCALE){
uint64_t timeScaleVal = E.getValUInt(); uint64_t timeScaleVal = E.getValUInt();
myMeta.inputLocalVars["timescale"] = timeScaleVal; meta.inputLocalVars["timescale"] = timeScaleVal;
timeScale = ((double)timeScaleVal) / 1000000.0; timeScale = ((double)timeScaleVal) / 1000000.0;
} }
// Live streams stop parsing the header as soon as the first Cluster is encountered // Live streams stop parsing the header as soon as the first Cluster is encountered
@ -346,34 +351,35 @@ namespace Mist{
uint64_t tNum = B.getTrackNum(); uint64_t tNum = B.getTrackNum();
uint64_t newTime = lastClusterTime + B.getTimecode(); uint64_t newTime = lastClusterTime + B.getTimecode();
trackPredictor &TP = packBuf[tNum]; trackPredictor &TP = packBuf[tNum];
DTSC::Track &Trk = myMeta.tracks[tNum]; size_t idx = meta.trackIDToIndex(tNum, getpid());
bool isVideo = (Trk.type == "video"); bool isVideo = (M.getType(idx) == "video");
bool isAudio = (Trk.type == "audio"); bool isAudio = (M.getType(idx) == "audio");
bool isASS = (Trk.codec == "subtitle" && Trk.init.size()); bool isASS = (M.getCodec(idx) == "subtitle" && M.getInit(idx).size());
// If this is a new video keyframe, flush the corresponding trackPredictor // If this is a new video keyframe, flush the corresponding trackPredictor
if (isVideo && B.isKeyframe()){ if (isVideo && B.isKeyframe()){
while (TP.hasPackets(true)){ while (TP.hasPackets(true)){
packetData &C = TP.getPacketData(true); packetData &C = TP.getPacketData(true);
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key); meta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
TP.remove(); TP.remove();
} }
TP.flush(); TP.flush();
} }
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){ for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
if (frameNo){ if (frameNo){
if (Trk.codec == "AAC"){ if (M.getCodec(idx) == "AAC"){
newTime += (1000000 / Trk.rate) / timeScale; // assume ~1000 samples per frame newTime += (1000000 / M.getRate(idx)) / timeScale; // assume ~1000 samples per frame
}else if (Trk.codec == "MP3"){ }else if (M.getCodec(idx) == "MP3"){
newTime += (1152000 / Trk.rate) / timeScale; // 1152 samples per frame newTime += (1152000 / M.getRate(idx)) / timeScale; // 1152 samples per frame
}else if (Trk.codec == "DTS"){ }else if (M.getCodec(idx) == "DTS"){
// Assume 512 samples per frame (DVD default) // Assume 512 samples per frame (DVD default)
// actual amount can be calculated from data, but data // actual amount can be calculated from data, but data
// is not available during header generation... // is not available during header generation...
// See: http://www.stnsoft.com/DVD/dtshdr.html // See: http://www.stnsoft.com/DVD/dtshdr.html
newTime += (512000 / Trk.rate) / timeScale; newTime += (512000 / M.getRate(idx)) / timeScale;
}else{ }else{
newTime += 1 / timeScale; newTime += 1 / timeScale;
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str()); ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!",
M.getCodec(idx).c_str());
} }
} }
uint32_t frameSize = B.getFrameSize(frameNo); uint32_t frameSize = B.getFrameSize(frameNo);
@ -388,7 +394,7 @@ namespace Mist{
} }
while (TP.hasPackets()){ while (TP.hasPackets()){
packetData &C = TP.getPacketData(isVideo); packetData &C = TP.getPacketData(isVideo);
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key); meta.update(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.dsize, C.bpos, C.key);
TP.remove(); TP.remove();
} }
} }
@ -398,23 +404,25 @@ namespace Mist{
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){ for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){
trackPredictor &TP = it->second; trackPredictor &TP = it->second;
while (TP.hasPackets(true)){ while (TP.hasPackets(true)){
packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video"); packetData &C =
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key); TP.getPacketData(M.getType(M.trackIDToIndex(it->first, getpid())) == "video");
meta.update(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.dsize, C.bpos, C.key);
TP.remove(); TP.remove();
} }
} }
} }
bench = Util::getMicros(bench); bench = Util::getMicros(bench);
INFO_MSG("Header generated in %llu ms", bench / 1000); INFO_MSG("Header generated in %" PRIu64 " ms", bench / 1000);
clearPredictors(); clearPredictors();
bufferedPacks = 0; bufferedPacks = 0;
myMeta.toFile(config->getString("input") + ".dtsh"); M.toFile(config->getString("input") + ".dtsh");
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); ++it){ std::set<size_t> validTracks = M.getValidTracks();
if (it->second.codec == "PCMLE"){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
it->second.codec = "PCM"; if (M.getCodec(*it) == "PCMLE"){
swapEndianness.insert(it->first); meta.setCodec(*it, "PCM");
swapEndianness.insert(*it);
} }
} }
return true; return true;
@ -422,7 +430,7 @@ namespace Mist{
void InputEBML::fillPacket(packetData &C){ void InputEBML::fillPacket(packetData &C){
if (swapEndianness.count(C.track)){ if (swapEndianness.count(C.track)){
switch (myMeta.tracks[C.track].size){ switch (M.getSize(M.trackIDToIndex(C.track, getpid()))){
case 16:{ case 16:{
char *ptr = C.ptr; char *ptr = C.ptr;
uint32_t ptrSize = C.dsize; uint32_t ptrSize = C.dsize;
@ -455,16 +463,18 @@ namespace Mist{
}break; }break;
} }
} }
thisPacket.genericFill(C.time, C.offset, C.track, C.ptr, C.dsize, C.bpos, C.key); thisPacket.genericFill(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.ptr, C.dsize,
C.bpos, C.key);
} }
void InputEBML::getNext(bool smart){ void InputEBML::getNext(size_t idx){
// Make sure we empty our buffer first // Make sure we empty our buffer first
if (bufferedPacks && packBuf.size()){ if (bufferedPacks && packBuf.size()){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){ for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){
trackPredictor &TP = it->second; trackPredictor &TP = it->second;
if (TP.hasPackets()){ if (TP.hasPackets()){
packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video"); packetData &C =
TP.getPacketData(M.getType(M.trackIDToIndex(it->first, getpid())) == "video");
fillPacket(C); fillPacket(C);
TP.remove(); TP.remove();
--bufferedPacks; --bufferedPacks;
@ -481,7 +491,7 @@ namespace Mist{
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){ for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){
trackPredictor &TP = it->second; trackPredictor &TP = it->second;
if (TP.hasPackets(true)){ if (TP.hasPackets(true)){
packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video"); packetData &C = TP.getPacketData(M.getType(M.trackIDToIndex(it->first, getpid())) == "video");
fillPacket(C); fillPacket(C);
TP.remove(); TP.remove();
--bufferedPacks; --bufferedPacks;
@ -494,7 +504,8 @@ namespace Mist{
return; return;
} }
B = EBML::Block(ptr); B = EBML::Block(ptr);
}while (!B || B.getType() != EBML::ELEM_BLOCK || !selectedTracks.count(B.getTrackNum())); }while (!B || B.getType() != EBML::ELEM_BLOCK ||
(idx != INVALID_TRACK_ID && M.getID(idx) != B.getTrackNum()));
}else{ }else{
B = EBML::Block(ptr); B = EBML::Block(ptr);
} }
@ -502,10 +513,10 @@ namespace Mist{
uint64_t tNum = B.getTrackNum(); uint64_t tNum = B.getTrackNum();
uint64_t newTime = lastClusterTime + B.getTimecode(); uint64_t newTime = lastClusterTime + B.getTimecode();
trackPredictor &TP = packBuf[tNum]; trackPredictor &TP = packBuf[tNum];
DTSC::Track &Trk = myMeta.tracks[tNum]; size_t trackIdx = M.trackIDToIndex(tNum, getpid());
bool isVideo = (Trk.type == "video"); bool isVideo = (M.getType(trackIdx) == "video");
bool isAudio = (Trk.type == "audio"); bool isAudio = (M.getType(trackIdx) == "audio");
bool isASS = (Trk.codec == "subtitle" && Trk.init.size()); bool isASS = (M.getCodec(trackIdx) == "subtitle" && M.getInit(trackIdx).size());
// If this is a new video keyframe, flush the corresponding trackPredictor // If this is a new video keyframe, flush the corresponding trackPredictor
if (isVideo && B.isKeyframe() && bufferedPacks){ if (isVideo && B.isKeyframe() && bufferedPacks){
@ -523,18 +534,19 @@ namespace Mist{
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){ for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
if (frameNo){ if (frameNo){
if (Trk.codec == "AAC"){ if (M.getCodec(trackIdx) == "AAC"){
newTime += (1000000 / Trk.rate) / timeScale; // assume ~1000 samples per frame newTime += (1000000 / M.getRate(trackIdx)) / timeScale; // assume ~1000 samples per frame
}else if (Trk.codec == "MP3"){ }else if (M.getCodec(trackIdx) == "MP3"){
newTime += (1152000 / Trk.rate) / timeScale; // 1152 samples per frame newTime += (1152000 / M.getRate(trackIdx)) / timeScale; // 1152 samples per frame
}else if (Trk.codec == "DTS"){ }else if (M.getCodec(trackIdx) == "DTS"){
// Assume 512 samples per frame (DVD default) // Assume 512 samples per frame (DVD default)
// actual amount can be calculated from data, but data // actual amount can be calculated from data, but data
// is not available during header generation... // is not available during header generation...
// See: http://www.stnsoft.com/DVD/dtshdr.html // See: http://www.stnsoft.com/DVD/dtshdr.html
newTime += (512000 / Trk.rate) / timeScale; newTime += (512000 / M.getRate(trackIdx)) / timeScale;
}else{ }else{
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str()); ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!",
M.getCodec(trackIdx).c_str());
} }
} }
uint32_t frameSize = B.getFrameSize(frameNo); uint32_t frameSize = B.getFrameSize(frameNo);
@ -560,22 +572,26 @@ namespace Mist{
}else{ }else{
// We didn't set thisPacket yet. Read another. // We didn't set thisPacket yet. Read another.
// Recursing is fine, this can only happen a few times in a row. // Recursing is fine, this can only happen a few times in a row.
getNext(smart); getNext(idx);
} }
} }
void InputEBML::seek(int seekTime){ void InputEBML::seek(uint64_t seekTime, size_t idx){
wantBlocks = true; wantBlocks = true;
clearPredictors(); clearPredictors();
bufferedPacks = 0; bufferedPacks = 0;
uint64_t mainTrack = getMainSelectedTrack(); uint64_t mainTrack = getMainSelectedTrack();
DTSC::Track Trk = myMeta.tracks[mainTrack];
bool isVideo = (Trk.type == "video"); DTSC::Keys keys(M.keys(mainTrack));
uint64_t seekPos = Trk.keys[0].getBpos(); DTSC::Parts parts(M.parts(mainTrack));
uint64_t seekPos = keys.getBpos(0);
// Replay the parts of the previous keyframe, so the timestaps match up // Replay the parts of the previous keyframe, so the timestaps match up
for (unsigned int i = 1; i < Trk.keys.size(); i++){ uint64_t partCount = 0;
if (Trk.keys[i].getTime() > seekTime){break;} for (size_t i = 0; i < keys.getEndValid(); i++){
seekPos = Trk.keys[i].getBpos(); if (keys.getTime(i) > seekTime){break;}
partCount += keys.getParts(i);
DONTEVEN_MSG("Seeking to %" PRIu64 ", found %" PRIu64 "...", seekTime, keys.getTime(i));
seekPos = keys.getBpos(i);
} }
Util::fseek(inFile, seekPos, SEEK_SET); Util::fseek(inFile, seekPos, SEEK_SET);
} }

View file

@ -11,14 +11,7 @@ namespace Mist{
uint64_t time, offset, track, dsize, bpos; uint64_t time, offset, track, dsize, bpos;
bool key; bool key;
Util::ResizeablePointer ptr; Util::ResizeablePointer ptr;
packetData(){ packetData() : time(0), offset(0), track(0), dsize(0), bpos(0), key(false){}
time = 0;
offset = 0;
track = 0;
dsize = 0;
bpos = 0;
key = false;
}
void set(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, void set(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, void *dataPtr = 0){ uint64_t packBytePos, bool isKeyframe, void *dataPtr = 0){
time = packTime; time = packTime;
@ -132,10 +125,12 @@ namespace Mist{
p.offset = ((uint32_t)((frameOffset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame; p.offset = ((uint32_t)((frameOffset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame;
} }
lastTime = p.time; lastTime = p.time;
INSANE_MSG("Outputting%s %llu+%llu (#%llu, Max=%llu), display at %llu", (p.key ? "KEY" : ""), INSANE_MSG("Outputting%s %" PRIu64 "+%" PRIu64 " (#%" PRIu64 ", Max=%" PRIu64
p.time, p.offset, rem, maxOffset, p.time + p.offset); "), display at %" PRIu64,
(p.key ? "KEY" : ""), p.time, p.offset, rem, maxOffset, p.time + p.offset);
return p; return p;
} }
void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, bool isVideo, void *dataPtr = 0){ uint64_t packBytePos, bool isKeyframe, bool isVideo, void *dataPtr = 0){
if (!ctr){lowestTime = packTime;} if (!ctr){lowestTime = packTime;}
@ -155,13 +150,16 @@ namespace Mist{
bool needsLock(); bool needsLock();
protected: protected:
virtual size_t streamByteCount(){
return totalBytes;
}; // For live streams: to update the stats with correct values.
void fillPacket(packetData &C); void fillPacket(packetData &C);
bool checkArguments(); bool checkArguments();
bool preRun(); bool preRun();
bool readHeader(); bool readHeader();
bool readElement(); bool readElement();
void getNext(bool smart = true); void getNext(size_t idx = INVALID_TRACK_ID);
void seek(int seekTime); void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
void clearPredictors(); void clearPredictors();
FILE *inFile; FILE *inFile;
Util::ResizeablePointer ptr; Util::ResizeablePointer ptr;
@ -177,6 +175,7 @@ namespace Mist{
bool needHeader(){return needsLock() && !readExistingHeader();} bool needHeader(){return needsLock() && !readExistingHeader();}
double timeScale; double timeScale;
bool wantBlocks; bool wantBlocks;
size_t totalBytes;
}; };
}// namespace Mist }// namespace Mist

View file

@ -28,6 +28,8 @@ namespace Mist{
capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("MP3");
} }
inputFLV::~inputFLV(){}
bool inputFLV::checkArguments(){ bool inputFLV::checkArguments(){
if (config->getString("input") == "-"){ if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl; std::cerr << "Input from stdin not yet supported" << std::endl;
@ -77,45 +79,49 @@ namespace Mist{
bool inputFLV::readHeader(){ bool inputFLV::readHeader(){
if (!inFile){return false;} if (!inFile){return false;}
meta.reInit(config->getString("streamname"));
// Create header file from FLV data // Create header file from FLV data
Util::fseek(inFile, 13, SEEK_SET); Util::fseek(inFile, 13, SEEK_SET);
AMF::Object amf_storage; AMF::Object amf_storage;
long long int lastBytePos = 13; uint64_t lastBytePos = 13;
uint64_t bench = Util::getMicros(); uint64_t bench = Util::getMicros();
while (!feof(inFile) && !FLV::Parse_Error){ while (!feof(inFile) && !FLV::Parse_Error){
if (tmpTag.FileLoader(inFile)){ if (tmpTag.FileLoader(inFile)){
tmpTag.toMeta(myMeta, amf_storage); tmpTag.toMeta(meta, amf_storage);
if (!tmpTag.getDataLen()){continue;} if (!tmpTag.getDataLen()){continue;}
if (tmpTag.needsInitData() && tmpTag.isInitData()){continue;} if (tmpTag.needsInitData() && tmpTag.isInitData()){continue;}
myMeta.update(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getDataLen(), size_t tNumber = meta.trackIDToIndex(tmpTag.getTrackID(), getpid());
lastBytePos, tmpTag.isKeyframe); if (tNumber != INVALID_TRACK_ID){
meta.update(tmpTag.tagTime(), tmpTag.offset(), tNumber, tmpTag.getDataLen(), lastBytePos,
tmpTag.isKeyframe);
}
lastBytePos = Util::ftell(inFile); lastBytePos = Util::ftell(inFile);
} }
} }
bench = Util::getMicros(bench); bench = Util::getMicros(bench);
INFO_MSG("Header generated in %llu ms: @%lld, %s, %s", bench / 1000, lastBytePos, INFO_MSG("Header generated in %" PRIu64 " ms: @%" PRIu64 ", %s, %s", bench / 1000, lastBytePos,
myMeta.vod ? "VoD" : "NOVoD", myMeta.live ? "Live" : "NOLive"); M.getVod() ? "VoD" : "NOVoD", M.getLive() ? "Live" : "NOLive");
if (FLV::Parse_Error){ if (FLV::Parse_Error){
tmpTag = FLV::Tag(); tmpTag = FLV::Tag();
FLV::Parse_Error = false; FLV::Parse_Error = false;
ERROR_MSG("Stopping at FLV parse error @%lld: %s", lastBytePos, FLV::Error_Str.c_str()); ERROR_MSG("Stopping at FLV parse error @%" PRIu64 ": %s", lastBytePos, FLV::Error_Str.c_str());
} }
myMeta.toFile(config->getString("input") + ".dtsh"); M.toFile(config->getString("input") + ".dtsh");
Util::fseek(inFile, 13, SEEK_SET); Util::fseek(inFile, 13, SEEK_SET);
return true; return true;
} }
void inputFLV::getNext(bool smart){ void inputFLV::getNext(size_t idx){
long long int lastBytePos = Util::ftell(inFile); uint64_t lastBytePos = Util::ftell(inFile);
if (selectedTracks.size() == 1){ if (idx != INVALID_TRACK_ID){
uint8_t targetTag = 0x08; uint8_t targetTag = 0x08;
if (selectedTracks.count(1)){targetTag = 0x09;} if (M.getType(idx) == "video"){targetTag = 0x09;}
if (selectedTracks.count(3)){targetTag = 0x12;} if (M.getType(idx) == "meta"){targetTag = 0x12;}
FLV::seekToTagType(inFile, targetTag); FLV::seekToTagType(inFile, targetTag);
} }
while (!feof(inFile) && !FLV::Parse_Error){ while (!feof(inFile) && !FLV::Parse_Error){
if (tmpTag.FileLoader(inFile)){ if (tmpTag.FileLoader(inFile)){
if (!selectedTracks.count(tmpTag.getTrackID())){ if (idx != INVALID_TRACK_ID && M.getID(idx) != tmpTag.getTrackID()){
lastBytePos = Util::ftell(inFile); lastBytePos = Util::ftell(inFile);
continue; continue;
} }
@ -129,22 +135,22 @@ namespace Mist{
if (FLV::Parse_Error){ if (FLV::Parse_Error){
FLV::Parse_Error = false; FLV::Parse_Error = false;
tmpTag = FLV::Tag(); tmpTag = FLV::Tag();
FAIL_MSG("FLV error @ %lld: %s", lastBytePos, FLV::Error_Str.c_str()); FAIL_MSG("FLV error @ %" PRIu64 ": %s", lastBytePos, FLV::Error_Str.c_str());
thisPacket.null(); thisPacket.null();
return; return;
} }
if (!tmpTag.getDataLen() || (tmpTag.needsInitData() && tmpTag.isInitData())){ if (!tmpTag.getDataLen() || (tmpTag.needsInitData() && tmpTag.isInitData())){
return getNext(); return getNext(idx);
} }
thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getData(), size_t tNumber = meta.trackIDToIndex(tmpTag.getTrackID(), getpid());
tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); // init packet from tmpTags data thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tNumber, tmpTag.getData(),
tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe);
DTSC::Track &trk = myMeta.tracks[tmpTag.getTrackID()]; if (M.getCodec(idx) == "PCM" && M.getSize(idx) == 16){
if (trk.codec == "PCM" && trk.size == 16){
char *ptr = 0; char *ptr = 0;
size_t ptrSize = 0; size_t ptrSize = 0;
thisPacket.getString("data", ptr, ptrSize); thisPacket.getString("data", ptr, ptrSize);
for (uint32_t i = 0; i < ptrSize; i += 2){ for (size_t i = 0; i < ptrSize; i += 2){
char tmpchar = ptr[i]; char tmpchar = ptr[i];
ptr[i] = ptr[i + 1]; ptr[i] = ptr[i + 1];
ptr[i + 1] = tmpchar; ptr[i + 1] = tmpchar;
@ -152,29 +158,12 @@ namespace Mist{
} }
} }
void inputFLV::seek(int seekTime){ void inputFLV::seek(uint64_t seekTime, size_t idx){
// We will seek to the corresponding keyframe of the video track if selected, otherwise audio // We will seek to the corresponding keyframe of the video track if selected, otherwise audio
// keyframe. Flv files are never multi-track, so track 1 is video, track 2 is audio. // keyframe. Flv files are never multi-track, so track 1 is video, track 2 is audio.
int trackSeek = (selectedTracks.count(1) ? 1 : 2); size_t seekTrack = (idx == INVALID_TRACK_ID ? M.mainTrack() : idx);
uint64_t seekPos = myMeta.tracks[trackSeek].keys[0].getBpos(); DTSC::Keys keys(M.keys(seekTrack));
for (unsigned int i = 0; i < myMeta.tracks[trackSeek].keys.size(); i++){ uint32_t keyNum = keys.getNumForTime(seekTime);
if (myMeta.tracks[trackSeek].keys[i].getTime() > seekTime){break;} Util::fseek(inFile, keys.getBpos(keyNum), SEEK_SET);
seekPos = myMeta.tracks[trackSeek].keys[i].getBpos();
}
Util::fseek(inFile, seekPos, SEEK_SET);
}
void inputFLV::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
}else{
trackSpec = "";
}
}
} }
}// namespace Mist }// namespace Mist

View file

@ -6,15 +6,15 @@ namespace Mist{
class inputFLV : public Input{ class inputFLV : public Input{
public: public:
inputFLV(Util::Config *cfg); inputFLV(Util::Config *cfg);
~inputFLV();
protected: protected:
// Private Functions // Private Functions
bool checkArguments(); bool checkArguments();
bool preRun(); bool preRun();
bool readHeader(); bool readHeader();
void getNext(bool smart = true); void getNext(size_t idx = INVALID_TRACK_ID);
void seek(int seekTime); void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
void trackSelect(std::string trackSpec);
bool keepRunning(); bool keepRunning();
FLV::Tag tmpTag; FLV::Tag tmpTag;
uint64_t lastModTime; uint64_t lastModTime;

View file

@ -11,6 +11,8 @@ namespace Mist{
bool checkArguments(){return false;}; bool checkArguments(){return false;};
bool readHeader(){return false;}; bool readHeader(){return false;};
bool needHeader(){return false;}; bool needHeader(){return false;};
void getNext(size_t idx = INVALID_TRACK_ID){}
void seek(uint64_t time, size_t idx = INVALID_TRACK_ID){}
}; };
}// namespace Mist }// namespace Mist

View file

@ -17,10 +17,9 @@ namespace Mist{
inputProcess = 0; inputProcess = 0;
} }
bool InputH264::preRun(){ bool InputH264::openStreamSource(){
if (config->getString("input") != "-"){ if (config->getString("input") != "-"){
std::string input = config->getString("input"); std::string input = config->getString("input");
const char *argv[2];
input = input.substr(10); input = input.substr(10);
char *args[128]; char *args[128];
@ -50,15 +49,20 @@ namespace Mist{
myConn.open(fileno(stdout), fileno(stdin)); myConn.open(fileno(stdout), fileno(stdin));
} }
myConn.Received().splitter.assign("\000\000\001", 3); myConn.Received().splitter.assign("\000\000\001", 3);
myMeta.vod = false;
myMeta.live = true;
myMeta.tracks[1].type = "video";
myMeta.tracks[1].codec = "H264";
myMeta.tracks[1].trackID = 1;
waitsSinceData = 0;
return true; return true;
} }
void InputH264::parseStreamHeader(){
tNumber = meta.addTrack();
meta.setType(tNumber, "video");
meta.setCodec(tNumber, "H264");
meta.setID(tNumber, tNumber);
waitsSinceData = 0;
INFO_MSG("Waiting for init data...");
while (myConn && !M.getInit(tNumber).size()){getNext();}
INFO_MSG("Init data received!");
}
bool InputH264::checkArguments(){ bool InputH264::checkArguments(){
std::string input = config->getString("input"); std::string input = config->getString("input");
if (input != "-" && input.substr(0, 10) != "h264-exec:"){ if (input != "-" && input.substr(0, 10) != "h264-exec:"){
@ -68,7 +72,7 @@ namespace Mist{
return true; return true;
} }
void InputH264::getNext(bool smart){ void InputH264::getNext(size_t idx){
do{ do{
if (!myConn.spool()){ if (!myConn.spool()){
Util::sleep(25); Util::sleep(25);
@ -87,18 +91,18 @@ namespace Mist{
while (nalSize && NAL.data()[nalSize - 1] == 0){--nalSize;} while (nalSize && NAL.data()[nalSize - 1] == 0){--nalSize;}
if (!nalSize){continue;} if (!nalSize){continue;}
uint8_t nalType = NAL.data()[0] & 0x1F; uint8_t nalType = NAL.data()[0] & 0x1F;
INSANE_MSG("NAL unit, type %u, size %lu", nalType, nalSize); INSANE_MSG("NAL unit, type %u, size %" PRIu32, nalType, nalSize);
if (nalType == 7 || nalType == 8){ if (nalType == 7 || nalType == 8){
if (nalType == 7){spsInfo = NAL.substr(0, nalSize);} if (nalType == 7){spsInfo = NAL.substr(0, nalSize);}
if (nalType == 8){ppsInfo = NAL.substr(0, nalSize);} if (nalType == 8){ppsInfo = NAL.substr(0, nalSize);}
if (!myMeta.tracks[1].init.size() && spsInfo.size() && ppsInfo.size()){ if (!meta.getInit(tNumber).size() && spsInfo.size() && ppsInfo.size()){
h264::sequenceParameterSet sps(spsInfo.data(), spsInfo.size()); h264::sequenceParameterSet sps(spsInfo.data(), spsInfo.size());
h264::SPSMeta spsChar = sps.getCharacteristics(); h264::SPSMeta spsChar = sps.getCharacteristics();
myMeta.tracks[1].width = spsChar.width; meta.setWidth(tNumber, spsChar.width);
myMeta.tracks[1].height = spsChar.height; meta.setHeight(tNumber, spsChar.height);
myMeta.tracks[1].fpks = spsChar.fps * 1000; meta.setFpks(tNumber, spsChar.fps * 1000);
if (myMeta.tracks[1].fpks < 100 || myMeta.tracks[1].fpks > 1000000){ if (M.getFpks(tNumber) < 100 || M.getFpks(tNumber) > 1000000){
myMeta.tracks[1].fpks = 0; meta.setFpks(tNumber, 0);
} }
MP4::AVCC avccBox; MP4::AVCC avccBox;
avccBox.setVersion(1); avccBox.setVersion(1);
@ -109,14 +113,14 @@ namespace Mist{
avccBox.setSPS(spsInfo); avccBox.setSPS(spsInfo);
avccBox.setPPSCount(1); avccBox.setPPSCount(1);
avccBox.setPPS(ppsInfo); avccBox.setPPS(ppsInfo);
myMeta.tracks[1].init = std::string(avccBox.payload(), avccBox.payloadSize()); meta.setInit(tNumber, avccBox.payload(), avccBox.payloadSize());
} }
continue; continue;
} }
if (myMeta.tracks[1].init.size()){ if (M.getInit(tNumber).size()){
uint64_t ts = Util::bootMS() - startTime; uint64_t ts = Util::bootMS() - startTime;
if (myMeta.tracks[1].fpks){ts = frameCount * (1000000 / myMeta.tracks[1].fpks);} if (M.getFpks(tNumber)){ts = frameCount * (1000000 / M.getFpks(tNumber));}
thisPacket.genericFill(ts, 0, 1, 0, 0, 0, h264::isKeyframe(NAL.data(), nalSize)); thisPacket.genericFill(ts, 0, tNumber, 0, 0, 0, h264::isKeyframe(NAL.data(), nalSize));
thisPacket.appendNal(NAL.data(), nalSize); thisPacket.appendNal(NAL.data(), nalSize);
++frameCount; ++frameCount;
return; return;

View file

@ -8,24 +8,24 @@ namespace Mist{
InputH264(Util::Config *cfg); InputH264(Util::Config *cfg);
protected: protected:
virtual bool needHeader(){return false;}
bool checkArguments(); bool checkArguments();
bool preRun(); void getNext(size_t idx = INVALID_TRACK_ID);
void getNext(bool smart = true);
Socket::Connection myConn; Socket::Connection myConn;
std::string ppsInfo; std::string ppsInfo;
std::string spsInfo; std::string spsInfo;
uint64_t frameCount; uint64_t frameCount;
// Empty defaults // Empty defaults
bool readHeader(){return true;} bool readHeader(){return true;}
bool openStreamSource(){return true;} bool openStreamSource();
void closeStreamSource(){} void closeStreamSource(){}
void parseStreamHeader(){} void parseStreamHeader();
void seek(int seekTime){} void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){}
void trackSelect(std::string trackSpec){}
bool needsLock(){return false;} bool needsLock(){return false;}
uint64_t startTime; uint64_t startTime;
pid_t inputProcess; pid_t inputProcess;
uint32_t waitsSinceData; uint32_t waitsSinceData;
size_t tNumber;
}; };
}// namespace Mist }// namespace Mist

View file

@ -118,7 +118,7 @@ namespace Mist{
/// Called by the global callbackFunc, to prevent timeouts /// Called by the global callbackFunc, to prevent timeouts
bool inputHLS::callback(){ bool inputHLS::callback(){
if (nProxy.userClient.isAlive()){nProxy.userClient.keepAlive();} keepAlive();
return config->is_active; return config->is_active;
} }
@ -415,7 +415,7 @@ namespace Mist{
if (key == "TARGETDURATION"){ if (key == "TARGETDURATION"){
waitTime = atoi(val.c_str()) / 2; waitTime = atoi(val.c_str()) / 2;
if (waitTime < 5){waitTime = 5;} if (waitTime < 2){waitTime = 2;}
} }
if (key == "MEDIA-SEQUENCE"){fileNo = atoll(val.c_str());} if (key == "MEDIA-SEQUENCE"){fileNo = atoll(val.c_str());}
@ -520,12 +520,13 @@ namespace Mist{
memset(entry.keyAES, 0, 16); memset(entry.keyAES, 0, 16);
} }
if (!isUrl()){ if (!isUrl()){
std::ifstream fileSource; std::ifstream fileSource;
std::string test = root.link(entry.filename).getFilePath(); std::string test = root.link(entry.filename).getFilePath();
fileSource.open(test.c_str(), std::ios::ate | std::ios::binary); fileSource.open(test.c_str(), std::ios::ate | std::ios::binary);
if (!fileSource.good()){WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno));} if (!fileSource.good()){WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno));}
totalBytes += fileSource.tellg(); entry.byteEnd = fileSource.tellg();
totalBytes += entry.byteEnd;
} }
entry.timestamp = lastTimestamp + startTime; entry.timestamp = lastTimestamp + startTime;
@ -588,20 +589,6 @@ namespace Mist{
return true; return true;
} }
void inputHLS::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
}else{
trackSpec = "";
}
}
}
void inputHLS::parseStreamHeader(){ void inputHLS::parseStreamHeader(){
if (!initPlaylist(config->getString("input"))){ if (!initPlaylist(config->getString("input"))){
FAIL_MSG("Failed to load HLS playlist, aborting"); FAIL_MSG("Failed to load HLS playlist, aborting");
@ -668,6 +655,8 @@ namespace Mist{
}while (!segDowner.atEnd()); }while (!segDowner.atEnd());
if (preCounter < counter){break;}// We're done reading this playlist! if (preCounter < counter){break;}// We're done reading this playlist!
} }
in.close();
} }
tsStream.clear(); tsStream.clear();
currentPlaylist = 0; currentPlaylist = 0;
@ -681,13 +670,12 @@ namespace Mist{
bool hasHeader = false; bool hasHeader = false;
// See whether a separate header file exists. // See whether a separate header file exists.
DTSC::File tmp(config->getString("input") + ".dtsh"); meta.reInit(config->getString("streamname"), config->getString("input") + ".dtsh");
if (tmp){ hasHeader = (bool)M;
myMeta = tmp.getMeta();
if (myMeta){hasHeader = true;}
}
if (!hasHeader){myMeta = DTSC::Meta();} if (M){return true;}
if (!hasHeader){meta.reInit(config->getString("streamname"), true);}
TS::Packet packet; // to analyse and extract data TS::Packet packet; // to analyse and extract data
@ -728,19 +716,22 @@ namespace Mist{
counter++; counter++;
} }
if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){ size_t idx = M.trackIDToIndex(packetId, getpid());
tsStream.initializeMetadata(myMeta, tmpTrackId, packetId); INFO_MSG("PacketID: %" PRIu64 ", pid: %d, mapped to %zu", packetId, getpid(), idx);
if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
tsStream.initializeMetadata(meta, tmpTrackId, packetId);
INFO_MSG("InitializingMeta for track %zu -> %zu", tmpTrackId, packetId);
idx = M.trackIDToIndex(packetId, getpid());
} }
if (!hasHeader){ if (!hasHeader){
headerPack.getString("data", data, dataLen); headerPack.getString("data", data, dataLen);
uint64_t pBPos = headerPack.getInt("bpos");
// keyframe data exists, so always add 19 bytes keyframedata. // keyframe data exists, so always add 19 bytes keyframedata.
long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0; uint32_t packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0;
long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11; size_t packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11;
myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId, meta.update(headerPack.getTime(), packOffset, idx, dataLen, entId,
headerPack.hasMember("keyframe"), packSendSize); headerPack.hasMember("keyframe"), packSendSize);
} }
} }
@ -766,19 +757,18 @@ namespace Mist{
counter++; counter++;
} }
if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){ if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
tsStream.initializeMetadata(myMeta, tmpTrackId, packetId); tsStream.initializeMetadata(meta, tmpTrackId, packetId);
idx = M.trackIDToIndex(packetId, getpid());
} }
if (!hasHeader){ if (!hasHeader){
headerPack.getString("data", data, dataLen); headerPack.getString("data", data, dataLen);
uint64_t pBPos = headerPack.getInt("bpos");
// keyframe data exists, so always add 19 bytes keyframedata. // keyframe data exists, so always add 19 bytes keyframedata.
long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0; long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0;
long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11; long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11;
myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId, meta.update(headerPack.getTime(), packOffset, idx, dataLen, entId,
headerPack.hasMember("keyframe"), packSendSize); headerPack.hasMember("keyframe"), packSendSize);
} }
tsStream.getEarliestPacket(headerPack); tsStream.getEarliestPacket(headerPack);
} }
@ -790,10 +780,8 @@ namespace Mist{
if (streamIsLive){return true;} if (streamIsLive){return true;}
INFO_MSG("write header file..."); INFO_MSG("write header file...");
std::ofstream oFile((config->getString("input") + ".dtsh").c_str()); M.toFile((config->getString("input") + ".dtsh").c_str());
in.close();
oFile << myMeta.toJSON().toNetPacked();
oFile.close();
return true; return true;
} }
@ -802,34 +790,30 @@ namespace Mist{
bool inputHLS::openStreamSource(){return true;} bool inputHLS::openStreamSource(){return true;}
void inputHLS::getNext(bool smart){ void inputHLS::getNext(size_t idx){
INSANE_MSG("Getting next"); INSANE_MSG("Getting next");
uint32_t tid = 0; uint32_t tid = 0;
bool finished = false; bool finished = false;
if (selectedTracks.size()){tid = *selectedTracks.begin();} if (userSelect.size()){tid = userSelect.begin()->first;}
thisPacket.null(); thisPacket.null();
while (config->is_active && (needsLock() || nProxy.userClient.isAlive())){ while (config->is_active && (needsLock() || keepAlive())){
// Check if we have a packet // Check if we have a packet
bool hasPacket = false; bool hasPacket = false;
if (streamIsLive){ if (streamIsLive){
hasPacket = tsStream.hasPacketOnEachTrack() || (segDowner.atEnd() && tsStream.hasPacket()); hasPacket = tsStream.hasPacketOnEachTrack() || (segDowner.atEnd() && tsStream.hasPacket());
}else{ }else{
hasPacket = tsStream.hasPacket(getMappedTrackId(tid)); hasPacket = tsStream.hasPacket(M.getID(idx) & 0xFFFF);
} }
// Yes? Excellent! Read and return it. // Yes? Excellent! Read and return it.
if (hasPacket){ if (hasPacket){
// Read // Read
if (myMeta.live){ if (M.getLive()){
tsStream.getEarliestPacket(thisPacket); tsStream.getEarliestPacket(thisPacket);
tid = getOriginalTrackId(currentPlaylist, thisPacket.getTrackId()); tid = M.trackIDToIndex((((uint64_t)currentPlaylist) << 16) + thisPacket.getTrackId(), getpid());
if (!tid){
INFO_MSG("Track %" PRIu64 " on PLS %u -> %" PRIu32, thisPacket.getTrackId(), currentPlaylist, tid);
continue;
}
}else{ }else{
tsStream.getPacket(getMappedTrackId(tid), thisPacket); tsStream.getPacket(M.getID(idx) & 0xFFFF, thisPacket);
} }
if (!thisPacket){ if (!thisPacket){
FAIL_MSG("Could not getNext TS packet!"); FAIL_MSG("Could not getNext TS packet!");
@ -940,25 +924,19 @@ namespace Mist{
} }
// Note: bpos is overloaded here for playlist entry! // Note: bpos is overloaded here for playlist entry!
void inputHLS::seek(int seekTime){ void inputHLS::seek(uint64_t seekTime, size_t idx){
plsTimeOffset.clear(); plsTimeOffset.clear();
plsLastTime.clear(); plsLastTime.clear();
plsInterval.clear(); plsInterval.clear();
tsStream.clear(); tsStream.clear();
int trackId = 0; uint64_t trackId = M.getID(idx);
unsigned long plistEntry = 0xFFFFFFFFull; unsigned long plistEntry = 0;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
unsigned long thisBPos = 0; DTSC::Keys keys(M.keys(idx));
for (std::deque<DTSC::Key>::iterator keyIt = myMeta.tracks[*it].keys.begin(); for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); i++){
keyIt != myMeta.tracks[*it].keys.end(); keyIt++){ if (keys.getTime(i) > seekTime){break;}
if (keyIt->getTime() > seekTime){break;} plistEntry = keys.getBpos(i);
thisBPos = keyIt->getBpos();
}
if (thisBPos < plistEntry){
plistEntry = thisBPos;
trackId = *it;
}
} }
if (plistEntry < 1){ if (plistEntry < 1){
@ -995,7 +973,7 @@ namespace Mist{
} }
} }
int inputHLS::getEntryId(int playlistId, uint64_t bytePos){ size_t inputHLS::getEntryId(uint32_t playlistId, uint64_t bytePos){
if (bytePos == 0){return 0;} if (bytePos == 0){return 0;}
tthread::lock_guard<tthread::mutex> guard(entryMutex); tthread::lock_guard<tthread::mutex> guard(entryMutex);
for (int i = 0; i < listEntries[playlistId].size(); i++){ for (int i = 0; i < listEntries[playlistId].size(); i++){
@ -1248,9 +1226,9 @@ namespace Mist{
/// return the playlist id from which we need to read the first upcoming segment /// return the playlist id from which we need to read the first upcoming segment
/// by timestamp. /// by timestamp.
/// this will keep the playlists in sync while reading segments. /// this will keep the playlists in sync while reading segments.
int inputHLS::firstSegment(){ size_t inputHLS::firstSegment(){
// Only one selected? Immediately return the right playlist. // Only one selected? Immediately return the right playlist.
if (selectedTracks.size() == 1){return getMappedTrackPlaylist(*selectedTracks.begin());} if (userSelect.size() == 1){return ((M.getID(userSelect.begin()->first) >> 16) & 0xFFFF);}
uint64_t firstTimeStamp = 0; uint64_t firstTimeStamp = 0;
int tmpId = -1; int tmpId = -1;
int segCount = 0; int segCount = 0;

View file

@ -27,8 +27,8 @@ namespace Mist{
uint64_t bytePos; uint64_t bytePos;
uint64_t mUTC; ///< UTC unix millis timestamp of first packet, if known uint64_t mUTC; ///< UTC unix millis timestamp of first packet, if known
float duration; float duration;
unsigned int timestamp; uint64_t timestamp;
unsigned int wait; uint64_t wait;
char ivec[16]; char ivec[16];
char keyAES[16]; char keyAES[16];
}; };
@ -75,10 +75,10 @@ namespace Mist{
int noChangeCount; int noChangeCount;
uint64_t lastFileIndex; uint64_t lastFileIndex;
int waitTime; uint64_t waitTime;
PlaylistType playlistType; PlaylistType playlistType;
unsigned int lastTimestamp; uint64_t lastTimestamp;
unsigned int startTime; uint64_t startTime;
uint64_t nextUTC; ///< If non-zero, the UTC timestamp of the next segment on this playlist uint64_t nextUTC; ///< If non-zero, the UTC timestamp of the next segment on this playlist
char keyAES[16]; char keyAES[16];
std::map<std::string, std::string> keys; std::map<std::string, std::string> keys;
@ -103,7 +103,7 @@ namespace Mist{
int version; int version;
int targetDuration; int targetDuration;
bool endPlaylist; bool endPlaylist;
int currentPlaylist; uint64_t currentPlaylist;
bool allowRemap; ///< True if the next packet may remap the timestamps bool allowRemap; ///< True if the next packet may remap the timestamps
bool allowSoftRemap; ///< True if the next packet may soft-remap the timestamps bool allowSoftRemap; ///< True if the next packet may soft-remap the timestamps
@ -113,7 +113,7 @@ namespace Mist{
std::map<int, uint64_t> plsLastTime; std::map<int, uint64_t> plsLastTime;
std::map<int, uint64_t> plsInterval; std::map<int, uint64_t> plsInterval;
int currentIndex; size_t currentIndex;
std::string currentFile; std::string currentFile;
TS::Stream tsStream; ///< Used for parsing the incoming ts stream TS::Stream tsStream; ///< Used for parsing the incoming ts stream
@ -128,9 +128,9 @@ namespace Mist{
bool preSetup(); bool preSetup();
bool readHeader(); bool readHeader();
bool needHeader(){return true;} bool needHeader(){return true;}
void getNext(bool smart = true); void getNext(size_t idx = INVALID_TRACK_ID);
void seek(int seekTime); void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
void trackSelect(std::string trackSpec);
FILE *inFile; FILE *inFile;
FILE *tsFile; FILE *tsFile;
@ -141,10 +141,7 @@ namespace Mist{
void parseStreamHeader(); void parseStreamHeader();
uint32_t getMappedTrackId(uint64_t id); size_t getEntryId(uint32_t playlistId, uint64_t bytePos);
uint32_t getMappedTrackPlaylist(uint64_t id);
uint64_t getOriginalTrackId(uint32_t playlistId, uint32_t id);
int getEntryId(int playlistId, uint64_t bytePos);
}; };
}// namespace Mist }// namespace Mist

Some files were not shown because too many files have changed in this diff Show more