New Meta commit
This commit is contained in:
parent
fccf66fba2
commit
2b99f2f5ea
183 changed files with 13333 additions and 14421 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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
302
lib/cmaf.cpp
Normal 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
12
lib/cmaf.h
Normal 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
440
lib/comms.cpp
Normal 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
194
lib/comms.h
Normal 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
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------- */
|
/* ---------------------------------------- */
|
||||||
|
|
|
@ -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;
|
||||||
|
|
3349
lib/dtsc.cpp
3349
lib/dtsc.cpp
File diff suppressed because it is too large
Load diff
627
lib/dtsc.h
627
lib/dtsc.h
|
@ -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
|
||||||
|
|
2123
lib/dtscmeta.cpp
2123
lib/dtscmeta.cpp
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
289
lib/flv_tag.cpp
289
lib/flv_tag.cpp
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
112
lib/rtp.cpp
112
lib/rtp.cpp
|
@ -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;
|
||||||
|
|
58
lib/rtp.h
58
lib/rtp.h
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
410
lib/sdp.cpp
410
lib/sdp.cpp
|
@ -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.
|
||||||
|
|
20
lib/sdp.h
20
lib/sdp.h
|
@ -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 ¶m) const;
|
std::string getParamString(const std::string ¶m) const;
|
||||||
uint64_t getParamInt(const std::string ¶m) const;
|
uint64_t getParamInt(const std::string ¶m) 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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
170
lib/stream.cpp
170
lib/stream.cpp
|
@ -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;
|
||||||
|
|
13
lib/stream.h
13
lib/stream.h
|
@ -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:
|
||||||
|
|
|
@ -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());
|
||||||
|
|
10
lib/stun.h
10
lib/stun.h
|
@ -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();
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> ¤tPos){
|
bool parseAdaptationSet(std::string &data, std::set<seekPos> ¤tPos){
|
||||||
// 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> ¤tPos){
|
||||||
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> ¤tPos){
|
||||||
// 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> ¤tPos){
|
||||||
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> ¤tPos){
|
||||||
// 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> ¤tPos){
|
||||||
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> ¤tPos, 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;}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -9,5 +9,5 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FLV::Tag flvData;
|
FLV::Tag flvData;
|
||||||
long long filter;
|
int64_t filter;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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] << " ";
|
||||||
|
|
|
@ -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++;
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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> ¤tPos){
|
|
||||||
// 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> ¤tPos, 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;
|
|
||||||
}
|
|
78
src/analysers/h264_translate.cpp
Normal file
78
src/analysers/h264_translate.cpp
Normal 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);
|
||||||
|
}
|
|
@ -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 == ""){
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
/// ~~~~~~~~~~~~~~~
|
/// ~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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++){
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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("+ "));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
1027
src/input/input.cpp
1027
src/input/input.cpp
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue