From 2b99f2f5ea683cf6042b0136af3da0fc145da3a1 Mon Sep 17 00:00:00 2001 From: Phencys Date: Wed, 21 Apr 2021 18:10:03 +0200 Subject: [PATCH] New Meta commit --- .gitignore | 2 + CMakeLists.txt | 78 +- lib/bitstream.cpp | 77 +- lib/bitstream.h | 22 +- lib/certificate.cpp | 1 - lib/cmaf.cpp | 302 ++ lib/cmaf.h | 12 + lib/comms.cpp | 440 +++ lib/comms.h | 194 + lib/config.cpp | 4 + lib/config.h | 4 + lib/defines.h | 90 +- lib/dtls_srtp_handshake.cpp | 20 +- lib/dtls_srtp_handshake.h | 12 +- lib/dtsc.cpp | 3349 ++++++++++++++--- lib/dtsc.h | 627 +-- lib/dtscmeta.cpp | 2123 ----------- lib/encryption.cpp | 343 +- lib/encryption.h | 47 +- lib/flv_tag.cpp | 289 +- lib/flv_tag.h | 11 +- lib/h264.cpp | 1 - lib/h264.h | 2 - lib/h265.cpp | 3 +- lib/http_parser.cpp | 3 +- lib/json.h | 1 - lib/mp4.h | 1 + lib/mp4_generic.cpp | 72 +- lib/mp4_generic.h | 18 +- lib/mpeg.cpp | 2 +- lib/rtp.cpp | 112 +- lib/rtp.h | 58 +- lib/rtp_fec.cpp | 13 +- lib/rtp_fec.h | 2 +- lib/sdp.cpp | 410 +- lib/sdp.h | 20 +- lib/sdp_media.cpp | 49 +- lib/sdp_media.h | 12 +- lib/shared_memory.cpp | 626 +-- lib/shared_memory.h | 138 +- lib/srtp.h | 6 +- lib/stream.cpp | 170 +- lib/stream.h | 13 +- lib/stun.cpp | 2 +- lib/stun.h | 10 +- lib/triggers.cpp | 28 +- lib/ts_packet.cpp | 113 +- lib/ts_packet.h | 33 +- lib/ts_stream.cpp | 182 +- lib/ts_stream.h | 7 +- lib/util.cpp | 3 + lib/util.h | 6 +- src/analysers/analyser.cpp | 6 +- src/analysers/analyser_dash.cpp | 215 +- src/analysers/analyser_dtsc.cpp | 69 +- src/analysers/analyser_ebml.cpp | 2 +- src/analysers/analyser_ebml.h | 2 +- src/analysers/analyser_flv.cpp | 3 +- src/analysers/analyser_flv.h | 2 +- src/analysers/analyser_h264.cpp | 4 +- src/analysers/analyser_hls.cpp | 8 +- src/analysers/analyser_mp4.cpp | 11 +- src/analysers/analyser_ogg.cpp | 10 +- src/analysers/analyser_ogg.h | 4 +- src/analysers/analyser_riff.cpp | 2 +- src/analysers/analyser_rtmp.cpp | 72 +- src/analysers/analyser_rtmp.h | 2 +- src/analysers/analyser_rtsp.cpp | 14 +- src/analysers/analyser_ts.cpp | 32 +- src/analysers/analyser_ts.h | 6 +- src/analysers/dash_analyser.cpp | 485 --- src/analysers/h264_translate.cpp | 78 + src/controller/controller.cpp | 25 + src/controller/controller_capabilities.cpp | 16 +- src/controller/controller_connectors.cpp | 13 +- src/controller/controller_license.cpp | 18 +- src/controller/controller_push.cpp | 5 +- src/controller/controller_statistics.cpp | 494 ++- src/controller/controller_statistics.h | 20 +- src/controller/controller_storage.cpp | 18 +- src/controller/controller_streams.cpp | 4 +- src/controller/controller_updater.cpp | 24 - src/input/input.cpp | 1027 ++--- src/input/input.h | 53 +- src/input/input_av.cpp | 10 +- src/input/input_av.h | 2 +- src/input/input_balancer.cpp | 2 +- src/input/input_buffer.cpp | 1044 ++--- src/input/input_buffer.h | 49 +- src/input/input_dtsc.cpp | 406 +- src/input/input_dtsc.h | 39 +- src/input/input_ebml.cpp | 166 +- src/input/input_ebml.h | 23 +- src/input/input_flv.cpp | 75 +- src/input/input_flv.h | 6 +- src/input/input_folder.h | 2 + src/input/input_h264.cpp | 44 +- src/input/input_h264.h | 12 +- src/input/input_hls.cpp | 124 +- src/input/input_hls.h | 25 +- src/input/input_ismv.cpp | 423 +-- src/input/input_ismv.h | 30 +- src/input/input_mp3.cpp | 55 +- src/input/input_mp3.h | 6 +- src/input/input_mp4.cpp | 243 +- src/input/input_mp4.h | 18 +- src/input/input_ogg.cpp | 316 +- src/input/input_ogg.h | 51 +- src/input/input_rtsp.cpp | 101 +- src/input/input_rtsp.h | 8 +- src/input/input_srt.cpp | 26 +- src/input/input_srt.h | 5 +- src/input/input_ts.cpp | 262 +- src/input/input_ts.h | 7 +- src/io.cpp | 839 ++--- src/io.h | 95 +- src/output/mist_out.cpp | 1 + src/output/noffmpeg.jpg | Bin 0 -> 13156 bytes src/output/noh264.jpg | Bin 0 -> 11143 bytes src/output/output.cpp | 1424 ++++--- src/output/output.h | 65 +- src/output/output_cmaf.cpp | 678 ++++ src/output/output_cmaf.h | 43 + src/output/output_dash_mp4.cpp | 613 --- src/output/output_dash_mp4.h | 30 - src/output/output_dtsc.cpp | 79 +- src/output/output_dtsc.h | 1 - src/output/output_ebml.cpp | 337 +- src/output/output_ebml.h | 26 +- ...put_progressive_flv.cpp => output_flv.cpp} | 71 +- ...{output_progressive_flv.h => output_flv.h} | 6 +- src/output/output_h264.cpp | 6 +- src/output/output_hds.cpp | 149 +- src/output/output_hds.h | 8 +- src/output/output_hls.cpp | 406 +- src/output/output_hls.h | 16 +- src/output/output_hss.cpp | 597 --- src/output/output_hss.h | 26 - src/output/output_http.cpp | 6 +- src/output/output_http_internal.cpp | 98 +- src/output/output_https.cpp | 15 +- src/output/output_httpts.cpp | 32 +- src/output/output_httpts.h | 2 +- src/output/output_jpg.cpp | 297 ++ src/output/output_jpg.h | 22 + src/output/output_json.cpp | 46 +- ...put_progressive_mp3.cpp => output_mp3.cpp} | 14 +- ...{output_progressive_mp3.h => output_mp3.h} | 6 +- src/output/output_mp4.cpp | 1144 ++++++ ...{output_progressive_mp4.h => output_mp4.h} | 46 +- src/output/output_ogg.cpp | 203 + src/output/output_ogg.h | 24 + src/output/output_progressive_mp4.cpp | 1183 ------ src/output/output_progressive_ogg.cpp | 205 - src/output/output_progressive_ogg.h | 24 - src/output/output_push.cpp | 327 -- src/output/output_push.h | 20 - src/output/output_raw.cpp | 4 +- src/output/output_rtmp.cpp | 433 +-- src/output/output_rtmp.h | 4 +- src/output/output_rtsp.cpp | 192 +- src/output/output_rtsp.h | 6 +- src/output/output_sanitycheck.cpp | 139 +- src/output/output_sanitycheck.h | 1 + src/output/output_srt.cpp | 43 +- src/output/output_srt.h | 10 +- src/output/output_ts.cpp | 128 +- src/output/output_ts.h | 9 +- src/output/output_ts_base.cpp | 82 +- src/output/output_ts_base.h | 16 +- src/output/output_wav.cpp | 40 +- src/output/output_webrtc.cpp | 373 +- src/output/output_webrtc.h | 83 +- src/process/process_exec.cpp | 35 +- src/process/process_exec.h | 46 +- src/process/process_ffmpeg.cpp | 99 +- src/process/process_ffmpeg.h | 4 +- src/relaccxsampler.cpp | 21 + src/utils/util_load.cpp | 118 +- src/utils/util_meta.cpp | 21 + src/utils/util_rax.cpp | 2 +- test/aes_ctr128.cpp | 105 - test/bitwriter.cpp | 23 + 183 files changed, 13333 insertions(+), 14421 deletions(-) create mode 100644 lib/cmaf.cpp create mode 100644 lib/cmaf.h create mode 100644 lib/comms.cpp create mode 100644 lib/comms.h delete mode 100644 lib/dtscmeta.cpp delete mode 100644 src/analysers/dash_analyser.cpp create mode 100644 src/analysers/h264_translate.cpp create mode 100644 src/output/noffmpeg.jpg create mode 100644 src/output/noh264.jpg create mode 100644 src/output/output_cmaf.cpp create mode 100644 src/output/output_cmaf.h delete mode 100644 src/output/output_dash_mp4.cpp delete mode 100644 src/output/output_dash_mp4.h rename src/output/{output_progressive_flv.cpp => output_flv.cpp} (62%) rename src/output/{output_progressive_flv.h => output_flv.h} (71%) delete mode 100644 src/output/output_hss.cpp delete mode 100644 src/output/output_hss.h create mode 100644 src/output/output_jpg.cpp create mode 100644 src/output/output_jpg.h rename src/output/{output_progressive_mp3.cpp => output_mp3.cpp} (80%) rename src/output/{output_progressive_mp3.h => output_mp3.h} (65%) create mode 100644 src/output/output_mp4.cpp rename src/output/{output_progressive_mp4.h => output_mp4.h} (63%) create mode 100644 src/output/output_ogg.cpp create mode 100644 src/output/output_ogg.h delete mode 100644 src/output/output_progressive_mp4.cpp delete mode 100644 src/output/output_progressive_ogg.cpp delete mode 100644 src/output/output_progressive_ogg.h delete mode 100644 src/output/output_push.cpp delete mode 100644 src/output/output_push.h create mode 100644 src/relaccxsampler.cpp create mode 100644 src/utils/util_meta.cpp delete mode 100644 test/aes_ctr128.cpp create mode 100644 test/bitwriter.cpp diff --git a/.gitignore b/.gitignore index d4788a64..1c26505b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,8 @@ libtool server.html* *.js.h *.css.h +noffmpeg.h +noh264.h .dirstamp *.orig *.lock diff --git a/CMakeLists.txt b/CMakeLists.txt index 908ed781..0b25d30e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ if(COMMAND cmake_policy) cmake_policy(SET CMP0003 NEW) endif(COMMAND cmake_policy) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++98") + SET(SOURCE_DIR ${PROJECT_SOURCE_DIR}) SET(BINARY_DIR ${PROJECT_BINARY_DIR}) 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}") 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 # @@ -145,6 +148,8 @@ set(libHeaders lib/bitstream.h lib/certificate.h lib/checksum.h + lib/cmaf.h + lib/comms.h lib/config.h lib/defines.h lib/dtls_srtp_handshake.h @@ -168,7 +173,6 @@ set(libHeaders lib/nal.h lib/ogg.h lib/procs.h - lib/rijndael.h lib/rtmpchunks.h lib/rtp_fec.h lib/rtp.h @@ -207,11 +211,12 @@ add_library (mist lib/encode.cpp lib/bitfields.cpp lib/bitstream.cpp + lib/cmaf.cpp + lib/comms.cpp lib/certificate.cpp lib/config.cpp lib/dtls_srtp_handshake.cpp lib/dtsc.cpp - lib/dtscmeta.cpp lib/encryption.cpp lib/flv_tag.cpp lib/h264.cpp @@ -230,7 +235,6 @@ add_library (mist lib/nal.cpp lib/ogg.cpp lib/procs.cpp - lib/rijndael.cpp lib/rtmpchunks.cpp lib/rtp_fec.cpp lib/rtp.cpp @@ -356,7 +360,8 @@ macro(makeUtil utilName utilFile) ) endmacro() -makeUtil(Stats stats) +#makeUtil(Stats stats) +makeUtil(META meta) makeUtil(RAX rax) makeUtil(AMF amf) makeUtil(Certbot certbot) @@ -365,6 +370,14 @@ if (DEFINED LOAD_BALANCE ) endif() #LTS_END +add_executable(MistTranslateH264 + src/analysers/h264_translate.cpp + ${BINARY_DIR}/mist/.headers +) +target_link_libraries(MistTranslateH264 + mist +) + ######################################## # MistServer - Inputs # ######################################## @@ -396,7 +409,6 @@ endmacro() makeInput(HLS hls) makeInput(DTSC dtsc) -makeInput(DTSCCrypt dtsccrypt) makeInput(MP3 mp3) makeInput(FLV flv) if (DEFINED WITH_AV ) @@ -432,6 +444,9 @@ macro(makeOutput outputName format) if (";${ARGN};" MATCHES ";ts;") SET(tsOutput src/output/output_ts_base.cpp) endif() + if (";${ARGN};" MATCHES ";jpg;") + SET(tsOutput generated/noffmpeg.h generated/noh264.h) + endif() add_executable(MistOut${outputName} src/output/mist_out.cpp src/output/output.cpp @@ -439,6 +454,7 @@ macro(makeOutput outputName format) src/io.cpp ${httpOutput} ${tsOutput} + ${mp4Output} ${BINARY_DIR}/mist/.headers ) set_target_properties(MistOut${outputName} @@ -455,21 +471,23 @@ endmacro() makeOutput(RTMP rtmp) makeOutput(DTSC dtsc) -makeOutput(OGG progressive_ogg http) -makeOutput(FLV progressive_flv http) -makeOutput(HTTPMinimalServer http_minimalserver http) -makeOutput(MP4 progressive_mp4 http) -makeOutput(MP3 progressive_mp3 http) +makeOutput(OGG ogg http) +makeOutput(FLV flv http) +makeOutput(HTTPMinimalServer http_minimalserver http) +makeOutput(MP4 mp4 http) +makeOutput(MP3 mp3 http) makeOutput(H264 h264 http) -makeOutput(HSS hss http) makeOutput(HDS hds http) makeOutput(SRT srt http) makeOutput(JSON json http) +if (DEFINED WITH_JPG ) +makeOutput(JPG jpg http jpg) +endif() makeOutput(TS ts ts) makeOutput(HTTPTS httpts http ts) makeOutput(HLS hls http ts) +makeOutput(CMAF cmaf http)#LTS makeOutput(EBML ebml) -makeOutput(Push push)#LTS makeOutput(RTSP rtsp)#LTS makeOutput(WAV wav)#LTS makeOutput(WebRTC webrtc http)#LTS @@ -501,7 +519,6 @@ target_link_libraries(MistProcMKVExec mist) if (NOT DEFINED NOSSL ) makeOutput(HTTPS https)#LTS endif() -makeOutput(DASH dash_mp4 http)#LTS if (DEFINED WITH_SANITY ) makeOutput(SanityCheck sanitycheck)#LTS @@ -586,6 +603,17 @@ else() endif() endif() +######################################## +# RelAccX Sampler # +######################################## + add_executable(RelAccXSampler + src/relaccxsampler.cpp + ${BINARY_DIR}/mist/.headers + ) + target_link_libraries(RelAccXSampler + mist + ) + ######################################## # Embed Code # ######################################## @@ -658,6 +686,18 @@ add_custom_command(OUTPUT generated/skin_videojs.css.h 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 # ######################################## @@ -742,15 +782,6 @@ add_custom_target(clean-all ######################################## # 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) target_link_libraries(urltest mist) add_test(URLTest COMMAND urltest) @@ -768,4 +799,7 @@ target_link_libraries(jsontest mist) add_test(JSONTest COMMAND jsontest) add_executable(resolvetest test/resolve.cpp ${BINARY_DIR}/mist/.headers) 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) diff --git a/lib/bitstream.cpp b/lib/bitstream.cpp index 8288ec9a..914a729c 100644 --- a/lib/bitstream.cpp +++ b/lib/bitstream.cpp @@ -1,3 +1,4 @@ +#include "bitfields.h" #include "bitstream.h" #include "defines.h" #include @@ -142,62 +143,40 @@ namespace Utils{ long long unsigned int bitstream::peekUExpGolomb(){return golombPeeker() - 1;} - bitWriter::bitWriter(){ - dataBuffer = NULL; - bufferSize = 0; - reallocate(0); - dataSize = 0; + bitWriter::bitWriter(){bitSize = 0;} + + size_t bitWriter::size() const{return bitSize;} + + void bitWriter::append(const std::string &val){ + for (size_t i = 0; i < val.size(); i++){append(val[i]);} } - bitWriter::~bitWriter(){ - if (dataBuffer != NULL){free(dataBuffer);} - } + void bitWriter::append(uint64_t val, size_t bitLength){ + static char buf[9]; - void bitWriter::reallocate(size_t newSize){ - size_t sizeBefore = bufferSize / 8; - char *tmp; - if (dataBuffer != NULL){ - tmp = (char *)realloc(dataBuffer, (newSize / 8) + 1); + uint32_t byteLength = ((bitSize + bitLength) / 8) + 1; + while (byteLength > p.size()){p.append("", 1);} + + int bitShift = (64 - bitLength) - (bitSize % 8); + + if (bitShift >= 0){ + Bit::htobll(buf, val << bitShift); }else{ - tmp = (char *)malloc((newSize / 8) + 1); - } - if (tmp){ - dataBuffer = tmp; - bufferSize = ((newSize / 8) + 1) * 8; - memset(dataBuffer + sizeBefore, 0x00, (bufferSize / 8) - sizeBefore); - }else{ - FAIL_MSG("Could not reallocate!!"); + Bit::htobll(buf, val >> (bitShift * -1)); + buf[8] = ((val << (8 + bitShift)) & 0xFF); } + + 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::append(uint64_t value, size_t bitLength){ - 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); - } + void bitWriter::clear(){ + p.assign("", 0); + bitSize = 0; } size_t bitWriter::UExpGolombEncodedSize(uint64_t value){ diff --git a/lib/bitstream.h b/lib/bitstream.h index 9d035714..873b354f 100644 --- a/lib/bitstream.h +++ b/lib/bitstream.h @@ -1,5 +1,6 @@ #pragma once #include "defines.h" +#include "util.h" #include namespace Utils{ @@ -52,23 +53,20 @@ namespace Utils{ class bitWriter{ public: bitWriter(); - ~bitWriter(); - size_t size(); - void append(uint64_t value, size_t bitLength); + size_t size() const; + void append(const std::string &val); + void append(uint64_t val, size_t bitLength = 8); void appendExpGolomb(int64_t value); void appendUExpGolomb(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: - void reallocate(size_t newSize); - void appendData(uint8_t data, size_t len); - - char *dataBuffer; - // NOTE: ALL SIZES IN BITS! - size_t bufferSize; - - size_t dataSize; + // void appendData(uint8_t data, size_t len); + size_t bitSize; + Util::ResizeablePointer p; }; class bitstreamLSBF{ diff --git a/lib/certificate.cpp b/lib/certificate.cpp index 5ad5c783..3543b41a 100644 --- a/lib/certificate.cpp +++ b/lib/certificate.cpp @@ -213,7 +213,6 @@ std::string Certificate::getFingerprintSha256(){ uint8_t fingerprint_raw[32] ={}; 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); diff --git a/lib/cmaf.cpp b/lib/cmaf.cpp new file mode 100644 index 00000000..82caa0e7 --- /dev/null +++ b/lib/cmaf.cpp @@ -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 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::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 diff --git a/lib/cmaf.h b/lib/cmaf.h new file mode 100644 index 00000000..7b151fd1 --- /dev/null +++ b/lib/cmaf.h @@ -0,0 +1,12 @@ +#include "dtsc.h" +#include "mp4_dash.h" +#include "mp4_generic.h" +#include + +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 diff --git a/lib/comms.cpp b/lib/comms.cpp new file mode 100644 index 00000000..bc7fc0e3 --- /dev/null +++ b/lib/comms.cpp @@ -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 diff --git a/lib/comms.h b/lib/comms.h new file mode 100644 index 00000000..af096f70 --- /dev/null +++ b/lib/comms.h @@ -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 diff --git a/lib/config.cpp b/lib/config.cpp index abc939ae..1eb43778 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -39,6 +39,7 @@ bool Util::Config::is_restarting = false; static Socket::Server *serv_sock_pointer = 0; uint32_t Util::Config::printDebugLevel = DEBUG; // std::string Util::Config::streamName; +std::string Util::Config::exitReason; std::string Util::listenInterface; uint32_t Util::listenPort = 0; @@ -427,6 +428,7 @@ void Util::Config::activate(){ sigaction(SIGHUP, &new_action, NULL); sigaction(SIGTERM, &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. sigaction(SIGCHLD, 0, &cur_action); 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; if (!is_active && ++ctr > 4){BACKTRACE;} #endif + logExitReason("Setting is_active to false due to received signal interrupt"); is_active = false; default: 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. // Loops are bad, m'kay? break; + case SIGFPE: break; } }// signal_handler diff --git a/lib/config.h b/lib/config.h index 8ceeecd3..e3d34a55 100644 --- a/lib/config.h +++ b/lib/config.h @@ -27,6 +27,10 @@ namespace Util{ static bool is_restarting; ///< Set to true when restarting, set to false on boot. static uint32_t printDebugLevel; static std::string streamName; ///< Used by debug messages to identify the stream name + static std::string exitReason; + static void logExitReason(const std::string &reason){ + if (!exitReason.size()){exitReason = reason;} + } // functions Config(); Config(std::string cmd); diff --git a/lib/defines.h b/lib/defines.h index d4ab3186..8d5bbbc3 100644 --- a/lib/defines.h +++ b/lib/defines.h @@ -110,12 +110,13 @@ static inline void show_stackframe(){} #endif -#ifndef SHM_DATASIZE -#define SHM_DATASIZE 20 -#endif +#define DTSH_FRAGMENT_SIZE 13 +#define DTSH_KEY_SIZE 25 +#define DTSH_PART_SIZE 9 -#define AUDIO_KEY_INTERVAL \ - 2000 ///< This define controls the keyframe interval for non-video tracks, such as audio and metadata tracks. +#ifndef SHM_DATASIZE +#define SHM_DATASIZE 40 +#endif #ifndef STATS_DELAY #define STATS_DELAY 15 @@ -143,10 +144,62 @@ static inline void show_stackframe(){} /// Does not affect live streams. #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 SHM_STREAM_INDEX "MstSTRM%s" //%s stream name #define SHM_STREAM_STATE "MstSTATE%s" //%s stream name #define SHM_STREAM_CONF "MstSCnf%s" //%s stream name #define SHM_GLOBAL_CONF "MstGlobalConfig" @@ -157,12 +210,7 @@ static inline void show_stackframe(){} #define STRMSTAT_READY 4 #define STRMSTAT_SHUTDOWN 5 #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 SEM_LIVE "/MstLIVE%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 SIMUL_TRACKS 20 +#define SIMUL_TRACKS 40 #ifndef UDP_API_HOST #define UDP_API_HOST "localhost" @@ -190,8 +238,20 @@ static inline void show_stackframe(){} #define UDP_API_PORT 4242 #endif -#define INVALID_TRACK_ID 0 // 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. #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 diff --git a/lib/dtls_srtp_handshake.cpp b/lib/dtls_srtp_handshake.cpp index 2f22960d..0c8e9e53 100644 --- a/lib/dtls_srtp_handshake.cpp +++ b/lib/dtls_srtp_handshake.cpp @@ -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. */ 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. */ -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 *)&rand_ctx, 0x00, sizeof(rand_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){ - 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 static std::ofstream ofs; @@ -392,19 +391,4 @@ static void print_mbedtls_debug_message(void *ctx, int level, const char *file, #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"; - } - } -} - /* ---------------------------------------- */ diff --git a/lib/dtls_srtp_handshake.h b/lib/dtls_srtp_handshake.h index b7ab8fc5..7250167e 100644 --- a/lib/dtls_srtp_handshake.h +++ b/lib/dtls_srtp_handshake.h @@ -18,8 +18,11 @@ class DTLSSRTPHandshake{ public: DTLSSRTPHandshake(); - int init(mbedtls_x509_crt *certificate, - 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 init(mbedtls_x509_crt *certificate, 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 shutdown(); int parse(const uint8_t *data, size_t nbytes); bool hasKeyingMaterial(); @@ -40,8 +43,9 @@ private: public: int (*write_callback)(const uint8_t *data, int *nbytes); - std::deque 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::string cipher; /* selected SRTP cipher. */ + std::deque 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::string cipher; /* selected SRTP cipher. */ std::string remote_key; std::string remote_salt; std::string local_key; diff --git a/lib/dtsc.cpp b/lib/dtsc.cpp index cea94211..1d734c6c 100644 --- a/lib/dtsc.cpp +++ b/lib/dtsc.cpp @@ -1,513 +1,2964 @@ /// \file dtsc.cpp /// Holds all code for DDVTECH Stream Container parsing/generation. +#include "bitfields.h" #include "defines.h" #include "dtsc.h" +#include "encode.h" #include //for htonl/ntohl -#include -#include //for memcmp -char DTSC::Magic_Header[] = "DTSC"; -char DTSC::Magic_Packet[] = "DTPD"; -char DTSC::Magic_Packet2[] = "DTP2"; -char DTSC::Magic_Command[] = "DTCM"; +#include +#include +#include +#include -DTSC::File::File(){ - F = 0; - buffer = malloc(4); - endPos = 0; -} +#define AUDIO_KEY_INTERVAL \ + 5000 ///< This define controls the keyframe interval for non-video tracks, such as audio and + ///< metadata tracks. -DTSC::File::File(const File &rhs){ - buffer = malloc(4); - *this = rhs; -} +namespace DTSC{ + char Magic_Header[] = "DTSC"; + char Magic_Packet[] = "DTPD"; + char Magic_Packet2[] = "DTP2"; + char Magic_Command[] = "DTCM"; -DTSC::File &DTSC::File::operator=(const File &rhs){ - created = rhs.created; - if (rhs.F){ - F = fdopen(dup(fileno(rhs.F)), (created ? "w+b" : "r+b")); - }else{ - F = 0; + // If non-zero, this variable will override any live jitter value calculations with the set value + uint64_t veryUglyJitterOverride = 0; + + /// Default constructor for packets - sets a null pointer and invalid packet. + Packet::Packet(){ + data = NULL; + bufferLen = 0; + dataLen = 0; + master = false; + version = DTSC_INVALID; + prevNalSize = 0; } - endPos = rhs.endPos; - if (rhs.myPack){myPack = rhs.myPack;} - metadata = rhs.metadata; - currtime = rhs.currtime; - lastreadpos = rhs.lastreadpos; - headerSize = rhs.headerSize; - trackMapping = rhs.trackMapping; - memcpy(buffer, rhs.buffer, 4); - return *this; -} -DTSC::File::operator bool() const{ - return F; -} - -/// Open a filename for DTSC reading/writing. -/// If create is true and file does not exist, attempt to create. -DTSC::File::File(std::string filename, bool create){ - buffer = malloc(8); - if (create){ - F = fopen(filename.c_str(), "w+b"); - if (!F){ - DEBUG_MSG(DLVL_ERROR, "Could not create file %s: %s", filename.c_str(), strerror(errno)); - return; + /// Copy constructor for packets, copies an existing packet with same noCopy flag as original. + Packet::Packet(const Packet &rhs, size_t idx){ + master = false; + bufferLen = 0; + data = NULL; + if (rhs.data && rhs.dataLen){ + reInit(rhs.data, rhs.dataLen); + if (idx != INVALID_TRACK_ID){Bit::htobl(data + 8, idx);} + }else{ + null(); } - // write an empty header - fseek(F, 0, SEEK_SET); - fwrite(DTSC::Magic_Header, 4, 1, F); - memset(buffer, 0, 4); - fwrite(buffer, 4, 1, F); // write 4 zero-bytes - headerSize = 0; - }else{ - F = fopen(filename.c_str(), "r+b"); } - created = create; - if (!F){ - HIGH_MSG("Could not open file %s", filename.c_str()); - return; - } - fseek(F, 0, SEEK_END); - endPos = ftell(F); - bool sepHeader = false; - if (!create){ - fseek(F, 0, SEEK_SET); - if (fread(buffer, 4, 1, F) != 1){ - DEBUG_MSG(DLVL_ERROR, "Can't read file contents of %s", filename.c_str()); - fclose(F); - F = 0; - return; + /// Data constructor for packets, either references or copies a packet from raw data. + Packet::Packet(const char *data_, unsigned int len, bool noCopy){ + master = false; + bufferLen = 0; + data = NULL; + reInit(data_, len, noCopy); + } + + /// This destructor clears frees the data pointer if the packet was not a reference. + Packet::~Packet(){ + if (master && data){free(data);} + } + + /// Copier for packets, copies an existing packet with same noCopy flag as original. + /// If going from copy to noCopy, this will free the data pointer first. + void Packet::operator=(const Packet &rhs){ + if (master && !rhs.master){null();} + if (rhs && rhs.data && rhs.dataLen){ + reInit(rhs.data, rhs.dataLen, !rhs.master); + }else{ + null(); } - if (memcmp(buffer, DTSC::Magic_Header, 4) != 0){ - if (memcmp(buffer, DTSC::Magic_Packet2, 4) != 0 && - memcmp(buffer, DTSC::Magic_Packet, 4) != 0 && memcmp(buffer, DTSC::Magic_Command, 4) != 0){ - DEBUG_MSG(DLVL_ERROR, "%s is not a valid DTSC file", filename.c_str()); - fclose(F); - F = 0; - return; + } + + /// Returns true if the packet is deemed valid, false otherwise. + /// Valid packets have a length of at least 8, known header type, and length equal to the length + /// set in the header. + Packet::operator bool() const{ + if (!data){ + DONTEVEN_MSG("No data"); + return false; + } + if (dataLen < 8){ + VERYHIGH_MSG("Datalen < 8"); + return false; + } + if (version == DTSC_INVALID){ + VERYHIGH_MSG("No valid version"); + return false; + } + if (ntohl(((int *)data)[1]) + 8 > dataLen){ + VERYHIGH_MSG("Length mismatch"); + return false; + } + return true; + } + + /// Returns the recognized packet type. + /// This type is set by reInit and all constructors, and then just referenced from there on. + packType Packet::getVersion() const{return version;} + + /// Resets this packet back to the same state as if it had just been freshly constructed. + /// If needed, this frees the data pointer. + void Packet::null(){ + if (master && data){free(data);} + master = false; + data = NULL; + bufferLen = 0; + dataLen = 0; + version = DTSC_INVALID; + } + + /// Internally used resize function for when operating in copy mode and the internal buffer is too + /// small. It will only resize up, never down. + ///\param len The length th scale the buffer up to if necessary + void Packet::resize(size_t len){ + if (master && len > bufferLen){ + char *tmp = (char *)realloc(data, len); + if (tmp){ + data = tmp; + bufferLen = len; }else{ - metadata.moreheader = -1; + FAIL_MSG("Out of memory on parsing a packet"); } } } - // we now know the first 4 bytes are DTSC::Magic_Header and we have a valid file - fseek(F, 4, SEEK_SET); - if (fread(buffer, 4, 1, F) != 1){ - fseek(F, 4, SEEK_SET); - memset(buffer, 0, 4); - fwrite(buffer, 4, 1, F); // write 4 zero-bytes - }else{ - headerSize = ntohl(((uint32_t *)buffer)[0]); - } - if (metadata.moreheader != -1){ - if (!sepHeader){ - readHeader(0); - fseek(F, 8 + headerSize, SEEK_SET); - }else{ - fseek(F, 0, SEEK_SET); + + void Packet::reInit(Socket::Connection &src){ + int sleepCount = 0; + null(); + int toReceive = 0; + while (src.connected()){ + if (!toReceive && src.Received().available(8)){ + if (src.Received().copy(2) != "DT"){ + WARN_MSG("Invalid DTSC Packet header encountered (%s)", + Encodings::Hex::encode(src.Received().copy(4)).c_str()); + break; + } + toReceive = Bit::btohl(src.Received().copy(8).data() + 4); + } + if (toReceive && src.Received().available(toReceive + 8)){ + std::string dataBuf = src.Received().remove(toReceive + 8); + reInit(dataBuf.data(), dataBuf.size()); + return; + } + if (!src.spool()){ + if (sleepCount++ > 150){ + WARN_MSG("Waiting for packet on connection timed out"); + return; + } + Util::wait(100); + } } - }else{ - fseek(F, 0, SEEK_SET); - File Fhead(filename + ".dtsh"); - if (Fhead){metadata = Fhead.metadata;} } - currframe = 0; -} -/// Returns the header metadata for this file as JSON::Value. -DTSC::Meta &DTSC::File::getMeta(){ - return metadata; -} + ///\brief Initializes a packet with new data + ///\param data_ The new data for the packet + ///\param len The length of the data pointed to by data_ + ///\param noCopy Determines whether to make a copy or not + void Packet::reInit(const char *data_, unsigned int len, bool noCopy){ + if (!data_){ + WARN_MSG("ReInit received a null pointer with len %d, nulling", len); + null(); + return; + } + if (!data_[0] && !data_[1] && !data_[2] && !data_[3]){ + null(); + return; + } + if (data_[0] != 'D' || data_[1] != 'T'){ + unsigned int twlen = len; + if (twlen > 20){twlen = 20;} + HIGH_MSG("ReInit received a pointer that didn't start with 'DT' but with %s (%u) - data " + "corruption?", + JSON::Value(std::string(data_, twlen)).toString().c_str(), len); + null(); + return; + } + if (len <= 0){len = ntohl(((int *)data_)[1]) + 8;} + // clear any existing controlled contents + if (master && noCopy){null();} + // set control flag to !noCopy + master = !noCopy; + // either copy the data, or only the pointer, depending on flag + if (noCopy){ + data = (char *)data_; + }else{ + resize(len); + memcpy(data, data_, len); + } + // check header type and store packet length + dataLen = len; + version = DTSC_INVALID; + if (len < 4){ + FAIL_MSG("ReInit received a packet with size < 4"); + return; + } + if (!memcmp(data, Magic_Packet2, 4)){version = DTSC_V2;} + if (!memcmp(data, Magic_Packet, 4)){version = DTSC_V1;} + if (!memcmp(data, Magic_Header, 4)){version = DTSC_HEAD;} + if (!memcmp(data, Magic_Command, 4)){version = DTCM;} + if (version == DTSC_INVALID){FAIL_MSG("ReInit received a packet with invalid header");} + } -/// (Re)writes the given string to the header area if the size is the same as the existing header. -/// Forces a write if force is set to true. -bool DTSC::File::writeHeader(std::string &header, bool force){ - if (headerSize != header.size() && !force){ - DEBUG_MSG(DLVL_ERROR, "Could not overwrite header - not equal size"); + /// Re-initializes this Packet to contain a generic DTSC packet with the given data fields. + /// When given a NULL pointer, the data is reserved and memset to 0 + /// If given a NULL pointer and a zero size, an empty packet is created. + void Packet::genericFill(uint64_t packTime, int64_t packOffset, uint32_t packTrack, const char *packData, + size_t packDataSize, uint64_t packBytePos, bool isKeyframe){ + null(); + master = true; + // time and trackID are part of the 20-byte header. + // the container object adds 4 bytes (plus 2+namelen for each content, see below) + // offset, if non-zero, adds 9 bytes (integer type) and 8 bytes (2+namelen) + // bpos, if >= 0, adds 9 bytes (integer type) and 6 bytes (2+namelen) + // keyframe, if true, adds 9 bytes (integer type) and 10 bytes (2+namelen) + // data adds packDataSize+5 bytes (string type) and 6 bytes (2+namelen) + if (packData && packDataSize < 1){ + FAIL_MSG("Attempted to fill a packet with %zu bytes for timestamp %" PRIu64 ", track %" PRIu32 "!", + packDataSize, packTime, packTrack); + return; + } + unsigned int sendLen = + 24 + (packOffset ? 17 : 0) + (packBytePos ? 15 : 0) + (isKeyframe ? 19 : 0) + packDataSize + 11; + resize(sendLen); + // set internal variables + version = DTSC_V2; + dataLen = sendLen; + // write the first 20 bytes + memcpy(data, "DTP2", 4); + Bit::htobl(data + 4, sendLen - 8); + Bit::htobl(data + 8, packTrack); + Bit::htobll(data + 12, packTime); + data[20] = 0xE0; // start container object + unsigned int offset = 21; + if (packOffset){ + memcpy(data + offset, "\000\006offset\001", 9); + Bit::htobll(data + offset + 9, packOffset); + offset += 17; + } + if (packBytePos){ + memcpy(data + offset, "\000\004bpos\001", 7); + Bit::htobll(data + offset + 7, packBytePos); + offset += 15; + } + if (isKeyframe){ + memcpy(data + offset, "\000\010keyframe\001\000\000\000\000\000\000\000\001", 19); + offset += 19; + } + memcpy(data + offset, "\000\004data\002", 7); + Bit::htobl(data + offset + 7, packDataSize); + memcpy(data + offset + 11, packData ? packData : 0, packDataSize); + // finish container with 0x0000EE + memcpy(data + offset + 11 + packDataSize, "\000\000\356", 3); + } + + /// sets the keyframe byte. + void Packet::setKeyFrame(bool kf){ + uint32_t offset = 23; + while (data[offset] != 'd' && data[offset] != 'k' && data[offset] != 'K'){ + switch (data[offset]){ + case 'o': offset += 17; break; + case 'b': offset += 15; break; + default: FAIL_MSG("Unknown field: %c", data[offset]); + } + } + + if (data[offset] == 'k' || data[offset] == 'K'){ + data[offset] = (kf ? 'k' : 'K'); + data[offset + 16] = (kf ? 1 : 0); + }else{ + ERROR_MSG("Could not set keyframe - field not found!"); + } + } + + void Packet::appendData(const char *appendData, uint32_t appendLen){ + resize(dataLen + appendLen); + memcpy(data + dataLen - 3, appendData, appendLen); + memcpy(data + dataLen - 3 + appendLen, "\000\000\356", 3); // end container + dataLen += appendLen; + Bit::htobl(data + 4, Bit::btohl(data + 4) + appendLen); + uint32_t offset = getDataStringLenOffset(); + Bit::htobl(data + offset, Bit::btohl(data + offset) + appendLen); + } + + void Packet::appendNal(const char *appendData, uint32_t appendLen){ + if (appendLen == 0){return;} + + resize(dataLen + appendLen + 4); + Bit::htobl(data + dataLen - 3, appendLen); + memcpy(data + dataLen - 3 + 4, appendData, appendLen); + memcpy(data + dataLen - 3 + 4 + appendLen, "\000\000\356", 3); // end container + dataLen += appendLen + 4; + Bit::htobl(data + 4, Bit::btohl(data + 4) + appendLen + 4); + uint32_t offset = getDataStringLenOffset(); + Bit::htobl(data + offset, Bit::btohl(data + offset) + appendLen + 4); + + prevNalSize = appendLen; + } + + void Packet::upgradeNal(const char *appendData, uint32_t appendLen){ + if (appendLen == 0){return;} + uint64_t sizeOffset = dataLen - 3 - 4 - prevNalSize; + if (Bit::btohl(data + sizeOffset) != prevNalSize){ + FAIL_MSG("PrevNalSize state not correct"); + return; + } + resize(dataLen + appendLen); // Not + 4 as size bytes have already been written here. + Bit::htobl(data + sizeOffset, prevNalSize + appendLen); + prevNalSize += appendLen; + memcpy(data + dataLen - 3, appendData, appendLen); + memcpy(data + dataLen - 3 + appendLen, "\000\000\356", 3); // end container + dataLen += appendLen; + Bit::htobl(data + 4, Bit::btohl(data + 4) + appendLen); + uint32_t offset = getDataStringLenOffset(); + Bit::htobl(data + offset, Bit::btohl(data + offset) + appendLen); + } + + size_t Packet::getDataStringLen(){return Bit::btohl(data + getDataStringLenOffset());} + + /// Method can only be used when using internal functions to build the data. + size_t Packet::getDataStringLenOffset(){ + size_t offset = 23; + while (data[offset] != 'd'){ + switch (data[offset]){ + case 'o': offset += 17; break; + case 'b': offset += 15; break; + case 'k': offset += 19; break; + case 'K': offset += 19; break; + default: FAIL_MSG("Unknown field: %c", data[offset]); return -1; + } + } + return offset + 5; + } + + /// Helper function for skipping over whole DTSC parts + static char *skipDTSC(char *p, char *max){ + if (p + 1 >= max || p[0] == 0x00){ + return 0; // out of packet! 1 == error + } + if (p[0] == DTSC_INT){ + // int, skip 9 bytes to next value + return p + 9; + } + if (p[0] == DTSC_STR){ + if (p + 4 >= max){ + return 0; // out of packet! + } + return p + 5 + Bit::btohl(p + 1); + } + if (p[0] == DTSC_OBJ || p[0] == DTSC_CON){ + p++; + // object, scan contents + while (p < max && p[0] + p[1] != 0){// while not encountering 0x0000 (we assume 0x0000EE) + if (p + 2 >= max){ + return 0; // out of packet! + } + p += 2 + Bit::btohs(p); // skip size + // otherwise, search through the contents, if needed, and continue + p = skipDTSC(p, max); + if (!p){return 0;} + } + return p + 3; + } + if (p[0] == DTSC_ARR){ + p++; + // array, scan contents + while (p < max && p[0] + p[1] != 0){// while not encountering 0x0000 (we assume 0x0000EE) + // search through contents... + p = skipDTSC(p, max); + if (!p){return 0;} + } + return p + 3; // skip end marker + } + return 0; // out of packet! 1 == error + } + + ///\brief Retrieves a single parameter as a string + ///\param identifier The name of the parameter + ///\param result A location on which the string will be returned + ///\param len An integer in which the length of the string will be returned + void Packet::getString(const char *identifier, char *&result, size_t &len) const{ + getScan().getMember(identifier).getString(result, len); + } + + ///\brief Retrieves a single parameter as a string + ///\param identifier The name of the parameter + ///\param result The string in which to store the result + void Packet::getString(const char *identifier, std::string &result) const{ + result = getScan().getMember(identifier).asString(); + } + + ///\brief Retrieves a single parameter as an integer + ///\param identifier The name of the parameter + ///\param result The result is stored in this integer + void Packet::getInt(const char *identifier, uint64_t &result) const{ + result = getScan().getMember(identifier).asInt(); + } + + ///\brief Retrieves a single parameter as an integer + ///\param identifier The name of the parameter + ///\result The requested parameter as an integer + uint64_t Packet::getInt(const char *identifier) const{ + uint64_t result; + getInt(identifier, result); + return result; + } + + ///\brief Retrieves a single parameter as a boolean + ///\param identifier The name of the parameter + ///\param result The result is stored in this boolean + void Packet::getFlag(const char *identifier, bool &result) const{ + uint64_t result_; + getInt(identifier, result_); + result = result_; + } + + ///\brief Retrieves a single parameter as a boolean + ///\param identifier The name of the parameter + ///\result The requested parameter as a boolean + bool Packet::getFlag(const char *identifier) const{ + bool result; + getFlag(identifier, result); + return result; + } + + ///\brief Checks whether a parameter exists + ///\param identifier The name of the parameter + ///\result Whether the parameter exists or not + bool Packet::hasMember(const char *identifier) const{ + return getScan().getMember(identifier).getType() > 0; + } + + ///\brief Returns the timestamp of the packet. + ///\return The timestamp of this packet. + uint64_t Packet::getTime() const{ + if (version != DTSC_V2){ + if (!data){return 0;} + return getInt("time"); + } + return Bit::btohll(data + 12); + } + + void Packet::setTime(uint64_t _time){ + if (!master){ + INFO_MSG("Can't set the time for this packet, as it is not master."); + return; + } + Bit::htobll(data + 12, _time); + } + + ///\brief Returns the track id of the packet. + ///\return The track id of this packet. + size_t Packet::getTrackId() const{ + if (version != DTSC_V2){return getInt("trackid");} + return Bit::btohl(data + 8); + } + + ///\brief Returns a pointer to the payload of this packet. + ///\return A pointer to the payload of this packet. + char *Packet::getData() const{return data;} + + ///\brief Returns the size of this packet. + ///\return The size of this packet. + uint64_t Packet::getDataLen() const{return dataLen;} + + ///\brief Returns the size of the payload of this packet. + ///\return The size of the payload of this packet. + size_t Packet::getPayloadLen() const{ + if (version == DTSC_V2){ + return dataLen - 20; + }else{ + return dataLen - 8; + } + } + + /// Returns a DTSC::Scan instance to the contents of this packet. + /// May return an invalid instance if this packet is invalid. + Scan Packet::getScan() const{ + if (!*this || !getDataLen() || !getPayloadLen() || getDataLen() <= getPayloadLen()){ + return Scan(); + } + return Scan(data + (getDataLen() - getPayloadLen()), getPayloadLen()); + } + + ///\brief Converts the packet into a JSON value + ///\return A JSON::Value representation of this packet. + JSON::Value Packet::toJSON() const{ + JSON::Value result; + uint32_t i = 8; + if (getVersion() == DTSC_V1){JSON::fromDTMI(data, dataLen, i, result);} + if (getVersion() == DTSC_V2){JSON::fromDTMI2(data, dataLen, i, result);} + return result; + } + + std::string Packet::toSummary() const{ + std::stringstream out; + char *res = 0; + size_t len = 0; + getString("data", res, len); + out << getTrackId() << "@" << getTime() << ": " << len << " bytes"; + if (hasMember("keyframe")){out << " (keyframe)";} + return out.str(); + } + + /// Create an invalid DTSC::Scan object by default. + Scan::Scan(){ + p = 0; + len = 0; + } + + /// Create a DTSC::Scan object from memory pointer. + Scan::Scan(char *pointer, size_t length){ + p = pointer; + len = length; + } + + /// Returns whether the DTSC::Scan object contains valid data. + Scan::operator bool() const{return (p && len);} + + /// Returns an object representing the named indice of this object. + /// Returns an invalid object if this indice doesn't exist or this isn't an object type. + Scan Scan::getMember(const std::string &indice) const{ + return getMember(indice.data(), indice.size()); + } + + /// Returns an object representing the named indice of this object. + /// Returns an invalid object if this indice doesn't exist or this isn't an object type. + Scan Scan::getMember(const char *indice, const size_t ind_len) const{ + if (getType() != DTSC_OBJ && getType() != DTSC_CON){return Scan();} + char *i = p + 1; + // object, scan contents + while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume 0x0000EE) + if (i + 2 >= p + len){ + return Scan(); // out of packet! + } + uint16_t strlen = Bit::btohs(i); + i += 2; + if (ind_len == strlen && strncmp(indice, i, strlen) == 0){ + return Scan(i + strlen, len - (i - p)); + } + i = skipDTSC(i + strlen, p + len); + if (!i){return Scan();} + } + return Scan(); + } + + /// Returns an object representing the named indice of this object. + /// Returns an invalid object if this indice doesn't exist or this isn't an object type. + bool Scan::hasMember(const std::string &indice) const{ + return getMember(indice.data(), indice.size()); + } + + /// Returns whether an object representing the named indice of this object exists. + /// Returns false if this indice doesn't exist or this isn't an object type. + bool Scan::hasMember(const char *indice, const size_t ind_len) const{ + return getMember(indice, ind_len); + } + + /// Returns an object representing the named indice of this object. + /// Returns an invalid object if this indice doesn't exist or this isn't an object type. + Scan Scan::getMember(const char *indice) const{return getMember(indice, strlen(indice));} + + /// Returns the amount of indices if an array, the amount of members if an object, or zero + /// otherwise. + size_t Scan::getSize() const{ + uint32_t arr_indice = 0; + if (getType() == DTSC_ARR){ + char *i = p + 1; + // array, scan contents + while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume + // 0x0000EE) + // search through contents... + arr_indice++; + i = skipDTSC(i, p + len); + if (!i){break;} + } + } + if (getType() == DTSC_OBJ || getType() == DTSC_CON){ + char *i = p + 1; + // object, scan contents + while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume + // 0x0000EE) + if (i + 2 >= p + len){ + return Scan(); // out of packet! + } + uint16_t strlen = Bit::btohs(i); + i += 2; + arr_indice++; + i = skipDTSC(i + strlen, p + len); + if (!i){break;} + } + } + return arr_indice; + } + + /// Returns an object representing the num-th indice of this array. + /// If not an array but an object, it returns the num-th member, instead. + /// Returns an invalid object if this indice doesn't exist or this isn't an array or object type. + Scan Scan::getIndice(size_t num) const{ + if (getType() == DTSC_ARR){ + char *i = p + 1; + unsigned int arr_indice = 0; + // array, scan contents + while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume + // 0x0000EE) + // search through contents... + if (arr_indice == num){ + return Scan(i, len - (i - p)); + }else{ + arr_indice++; + i = skipDTSC(i, p + len); + if (!i){return Scan();} + } + } + } + if (getType() == DTSC_OBJ || getType() == DTSC_CON){ + char *i = p + 1; + unsigned int arr_indice = 0; + // object, scan contents + while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume + // 0x0000EE) + if (i + 2 >= p + len){ + return Scan(); // out of packet! + } + unsigned int strlen = Bit::btohs(i); + i += 2; + if (arr_indice == num){ + return Scan(i + strlen, len - (i - p)); + }else{ + arr_indice++; + i = skipDTSC(i + strlen, p + len); + if (!i){return Scan();} + } + } + } + return Scan(); + } + + /// Returns the name of the num-th member of this object. + /// Returns an empty string on error or when not an object. + std::string Scan::getIndiceName(size_t num) const{ + if (getType() == DTSC_OBJ || getType() == DTSC_CON){ + char *i = p + 1; + unsigned int arr_indice = 0; + // object, scan contents + while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume + // 0x0000EE) + if (i + 2 >= p + len){ + return ""; // out of packet! + } + unsigned int strlen = Bit::btohs(i); + i += 2; + if (arr_indice == num){ + return std::string(i, strlen); + }else{ + arr_indice++; + i = skipDTSC(i + strlen, p + len); + if (!i){return "";} + } + } + } + return ""; + } + + /// Returns the first byte of this DTSC value, or 0 on error. + char Scan::getType() const{ + if (!p){return 0;} + return p[0]; + } + + /// Returns the boolean value of this DTSC value. + /// Numbers are compared to 0. + /// Strings are checked for non-zero length. + /// Objects and arrays are checked for content. + /// Returns false on error or in other cases. + bool Scan::asBool() const{ + switch (getType()){ + case DTSC_STR: return (p[1] | p[2] | p[3] | p[4]); + case DTSC_INT: return (asInt() != 0); + case DTSC_OBJ: + case DTSC_CON: + case DTSC_ARR: return (p[1] | p[2]); + default: return false; + } + } + + /// Returns the long long value of this DTSC number value. + /// Will convert string values to numbers, taking octal and hexadecimal types into account. + /// Illegal or invalid values return 0. + int64_t Scan::asInt() const{ + switch (getType()){ + case DTSC_INT: return Bit::btohll(p + 1); + case DTSC_STR: + char *str; + size_t strlen; + getString(str, strlen); + if (!strlen){return 0;} + return strtoll(str, 0, 0); + default: return 0; + } + } + + /// Returns the string value of this DTSC string value. + /// Uses getString internally, if a string. + /// Converts integer values to strings. + /// Returns an empty string on error. + std::string Scan::asString() const{ + switch (getType()){ + case DTSC_INT:{ + std::stringstream st; + st << asInt(); + return st.str(); + }break; + case DTSC_STR:{ + char *str; + size_t strlen; + getString(str, strlen); + return std::string(str, strlen); + }break; + } + return ""; + } + + /// Sets result to a pointer to the string, and strlen to the length of it. + /// Sets both to zero if this isn't a DTSC string value. + /// Attempts absolutely no conversion. + void Scan::getString(char *&result, size_t &strlen) const{ + if (getType() == DTSC_STR){ + result = p + 5; + strlen = Bit::btohl(p + 1); + return; + } + result = 0; + strlen = 0; + } + + /// Returns the DTSC scan object as a JSON value + /// Returns an empty object on error. + JSON::Value Scan::asJSON() const{ + JSON::Value result; + unsigned int i = 0; + JSON::fromDTMI(p, len, i, result); + return result; + } + + /// \todo Move this function to some generic area. Duplicate from json.cpp + static inline char hex2c(char c){ + if (c < 10){return '0' + c;} + if (c < 16){return 'A' + (c - 10);} + return '0'; + } + + /// \todo Move this function to some generic area. Duplicate from json.cpp + static std::string string_escape(const std::string val){ + std::stringstream out; + out << "\""; + for (size_t i = 0; i < val.size(); ++i){ + switch (val.data()[i]){ + case '"': out << "\\\""; break; + case '\\': out << "\\\\"; break; + case '\n': out << "\\n"; break; + case '\b': out << "\\b"; break; + case '\f': out << "\\f"; break; + case '\r': out << "\\r"; break; + case '\t': out << "\\t"; break; + default: + if (val.data()[i] < 32 || val.data()[i] > 126){ + out << "\\u00"; + out << hex2c((val.data()[i] >> 4) & 0xf); + out << hex2c(val.data()[i] & 0xf); + }else{ + out << val.data()[i]; + } + break; + } + } + out << "\""; + return out.str(); + } + + std::string Scan::toPrettyString(size_t indent) const{ + switch (getType()){ + case DTSC_STR:{ + uint32_t strlen = Bit::btohl(p + 1); + if (strlen > 250){ + std::stringstream ret; + ret << "\"" << strlen << " bytes of data\""; + return ret.str(); + } + return string_escape(asString()); + } + case DTSC_INT:{ + std::stringstream ret; + ret << asInt(); + return ret.str(); + } + case DTSC_OBJ: + case DTSC_CON:{ + std::stringstream ret; + ret << "{" << std::endl; + indent += 2; + char *i = p + 1; + bool first = true; + // object, scan contents + while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume + // 0x0000EE) + if (i + 2 >= p + len){ + indent -= 2; + ret << std::string(indent, ' ') << "}//walked out of object here"; + return ret.str(); + } + if (!first){ret << "," << std::endl;} + first = false; + uint16_t strlen = Bit::btohs(i); + i += 2; + ret << std::string(indent, ' ') << "\"" << std::string(i, strlen) + << "\": " << Scan(i + strlen, len - (i - p)).toPrettyString(indent); + i = skipDTSC(i + strlen, p + len); + if (!i){ + indent -= 2; + ret << std::string(indent, ' ') << "}//could not locate next object"; + return ret.str(); + } + } + indent -= 2; + ret << std::endl << std::string(indent, ' ') << "}"; + return ret.str(); + } + case DTSC_ARR:{ + std::stringstream ret; + ret << "[" << std::endl; + indent += 2; + Scan tmpScan; + unsigned int i = 0; + bool first = true; + do{ + tmpScan = getIndice(i++); + if (tmpScan.getType()){ + if (!first){ret << "," << std::endl;} + first = false; + ret << std::string(indent, ' ') << tmpScan.toPrettyString(indent); + } + }while (tmpScan.getType()); + indent -= 2; + ret << std::endl << std::string(indent, ' ') << "]"; + return ret.str(); + } + default: return "Error"; + } + } + + /// Initialize metadata from referenced DTSC::Scan object in master mode. + Meta::Meta(const std::string &_streamName, const DTSC::Scan &src){ + version = DTSH_VERSION; + streamMemBuf = 0; + isMemBuf = false; + isMaster = true; + reInit(_streamName, src); + } + + /// Initialize empty metadata, in master or slave mode. + /// If stream name is empty, slave mode is enforced. + Meta::Meta(const std::string &_streamName, bool master){ + if (!_streamName.size()){master = false;} + version = DTSH_VERSION; + streamMemBuf = 0; + isMemBuf = false; + isMaster = master; + reInit(_streamName, master); + } + + /// Initialize metadata from given DTSH file in master mode. + Meta::Meta(const std::string &_streamName, const std::string &fileName){ + version = DTSH_VERSION; + streamMemBuf = 0; + isMemBuf = false; + isMaster = true; + reInit(_streamName, fileName); + } + + void Meta::setMaster(bool _master){isMaster = _master;} + + bool Meta::getMaster() const{return isMaster;} + + /// Calls clear(), then initializes freshly. + /// If stream name is set, uses shared memory backing. + /// If stream name is empty, uses non-shared memory backing. + void Meta::reInit(const std::string &_streamName, bool master){ + clear(); + if (_streamName == ""){ + sBufMem(); + }else{ + sBufShm(_streamName, DEFAULT_TRACK_COUNT, master); + } + streamInit(); + } + + /// Calls clear(), then initializes from given DTSH file in master mode. + /// Internally calls reInit(const std::string&, const DTSC::Scan&). + /// If stream name is set, uses shared memory backing. + /// If stream name is empty, uses non-shared memory backing. + void Meta::reInit(const std::string &_streamName, const std::string &fileName){ + clear(); + + ///\todo Implement absence of keysizes here instead of input::parseHeader + std::ifstream inFile(fileName.c_str()); + if (!inFile.is_open()){return;} + inFile.seekg(0, std::ios_base::end); + size_t fileSize = inFile.tellg(); + inFile.seekg(0, std::ios_base::beg); + + char *scanBuf = (char *)malloc(fileSize); + inFile.read(scanBuf, fileSize); + + inFile.close(); + + size_t offset = 8; + if (!memcmp(scanBuf, "DTP2", 4)){offset = 20;} + + DTSC::Scan src(scanBuf + offset, fileSize - offset); + reInit(_streamName, src); + free(scanBuf); + } + + /// Calls clear(), then initializes from the given DTSC:Scan object in master mode. + /// If stream name is set, uses shared memory backing. + /// If stream name is empty, uses non-shared memory backing. + void Meta::reInit(const std::string &_streamName, const DTSC::Scan &src){ + clear(); + + if (_streamName == ""){ + sBufMem(); + }else{ + sBufShm(_streamName, DEFAULT_TRACK_COUNT, true); + } + streamInit(); + + setVod(src.hasMember("vod") && src.getMember("vod").asInt()); + + version = src.getMember("version").asInt(); + + if (src.hasMember("inputLocalVars")){ + inputLocalVars = JSON::fromString(src.getMember("inputLocalVars").asString()); + } + + size_t tNum = src.getMember("tracks").getSize(); + for (int i = 0; i < tNum; i++){ + DTSC::Scan trak = src.getMember("tracks").getIndice(i); + + char *fragStor; + char *keyStor; + char *partStor; + char *keySizeStor; + size_t fragLen; + size_t keyLen; + size_t partLen; + size_t keySizeLen; + trak.getMember("fragments").getString(fragStor, fragLen); + trak.getMember("keys").getString(keyStor, keyLen); + trak.getMember("parts").getString(partStor, partLen); + trak.getMember("keysizes").getString(keySizeStor, keySizeLen); + uint32_t fragCount = fragLen / DTSH_FRAGMENT_SIZE; + uint32_t keyCount = keyLen / DTSH_KEY_SIZE; + uint32_t partCount = partLen / DTSH_PART_SIZE; + size_t tIdx = addTrack(fragCount ? fragCount : DEFAULT_FRAGMENT_COUNT, keyCount ? keyCount : DEFAULT_KEY_COUNT, + partCount ? partCount : DEFAULT_PART_COUNT); + + setType(tIdx, trak.getMember("type").asString()); + setCodec(tIdx, trak.getMember("codec").asString()); + setInit(tIdx, trak.getMember("init").asString()); + setID(tIdx, trak.getMember("trackid").asInt()); + setFirstms(tIdx, trak.getMember("firstms").asInt()); + setLastms(tIdx, trak.getMember("lastms").asInt()); + setBps(tIdx, trak.getMember("bps").asInt()); + setMaxBps(tIdx, trak.getMember("maxbps").asInt()); + setSourceTrack(tIdx, INVALID_TRACK_ID); + if (trak.getMember("type").asString() == "video"){ + setWidth(tIdx, trak.getMember("width").asInt()); + setHeight(tIdx, trak.getMember("height").asInt()); + setFpks(tIdx, trak.getMember("fpks").asInt()); + + }else if (trak.getMember("type").asString() == "audio"){ + // rate channels size + setRate(tIdx, trak.getMember("rate").asInt()); + setChannels(tIdx, trak.getMember("channels").asInt()); + setSize(tIdx, trak.getMember("size").asInt()); + } + + Track &s = tracks[tIdx]; + + s.fragments.addRecords(fragCount); + uint64_t *vals = (uint64_t *)malloc(4 * fragCount * sizeof(uint64_t)); + for (int i = 0; i < fragCount; i++){ + char *ptr = fragStor + (i * DTSH_FRAGMENT_SIZE); + vals[i] = Bit::btohl(ptr); + vals[fragCount + i] = ptr[4]; + vals[(2 * fragCount) + i] = Bit::btohl(ptr + 5) - 1; + vals[(3 * fragCount) + i] = Bit::btohl(ptr + 9); + } + s.fragments.setInts("duration", vals, fragCount); + s.fragments.setInts("keys", vals + fragCount, fragCount); + s.fragments.setInts("firstkey", vals + (2 * fragCount), fragCount); + s.fragments.setInts("size", vals + (3 * fragCount), fragCount); + + vals = (uint64_t *)realloc(vals, 7 * keyCount * sizeof(uint64_t)); + s.keys.addRecords(keyCount); + uint64_t totalPartCount = 0; + for (int i = 0; i < keyCount; i++){ + char *ptr = keyStor + (i * DTSH_KEY_SIZE); + vals[i] = Bit::btohll(ptr); + vals[keyCount + i] = Bit::btoh24(ptr + 8); + vals[(2 * keyCount) + i] = Bit::btohl(ptr + 11); + vals[(3 * keyCount) + i] = Bit::btohs(ptr + 15); + vals[(4 * keyCount) + i] = Bit::btohll(ptr + 17); + vals[(5 * keyCount) + i] = Bit::btohl(keySizeStor + (i * 4)); // NOT WITH ptr!! + vals[(6 * keyCount) + i] = totalPartCount; + totalPartCount += vals[(3 * keyCount) + i]; + } + s.keys.setInts("bpos", vals, keyCount); + s.keys.setInts("duration", vals + keyCount, keyCount); + s.keys.setInts("number", vals + (2 * keyCount), keyCount); + s.keys.setInts("parts", vals + (3 * keyCount), keyCount); + s.keys.setInts("time", vals + (4 * keyCount), keyCount); + s.keys.setInts("size", vals + (5 * keyCount), keyCount); + s.keys.setInts("firstpart", vals + (6 * keyCount), keyCount); + + vals = (uint64_t *)realloc(vals, 3 * partCount * sizeof(uint64_t)); + s.parts.addRecords(partCount); + for (int i = 0; i < partCount; i++){ + char *ptr = partStor + (i * DTSH_PART_SIZE); + vals[i] = Bit::btoh24(ptr); + vals[partCount + i] = Bit::btoh24(ptr + 3); + vals[(2 * partCount) + i] = Bit::btoh24(ptr + 6); + } + s.parts.setInts("size", vals, partCount); + s.parts.setInts("duration", vals + partCount, partCount); + s.parts.setInts("offset", vals + (2 * partCount), partCount); + free(vals); + } + } + + /// Simply calls clear() + Meta::~Meta(){clear();} + + /// Switches the object to non-shared memory backed mode, with enough room for the given track + /// count. Should not be called repeatedly, nor to switch modes. + void Meta::sBufMem(size_t trackCount){ + size_t bufferSize = META_META_OFFSET + META_TRACK_OFFSET + META_META_RECORDSIZE + + (trackCount * META_TRACK_RECORDSIZE); + isMemBuf = true; + streamMemBuf = (char *)malloc(bufferSize); + memset(streamMemBuf, 0, bufferSize); + stream = Util::RelAccX(streamMemBuf, false); + } + + /// Initializes shared memory backed mode, with enough room for the given track count. + /// Should not be called repeatedly, nor to switch modes. + void Meta::sBufShm(const std::string &_streamName, size_t trackCount, bool master){ + isMaster = master; + if (isMaster){HIGH_MSG("Creating meta page for stream %s", _streamName.c_str());} + + size_t bufferSize = META_META_OFFSET + META_TRACK_OFFSET + META_META_RECORDSIZE + + (trackCount * META_TRACK_RECORDSIZE); + + isMemBuf = false; + streamName = _streamName; + + char pageName[NAME_BUFFER_SIZE]; + snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_META, streamName.c_str()); + + if (master){ + streamPage.init(pageName, bufferSize, false, false); + if (streamPage.mapped){ + FAIL_MSG("Re-claiming page %s", pageName); + BACKTRACE; + }else{ + streamPage.init(pageName, bufferSize, true); + } + streamPage.master = false; + stream = Util::RelAccX(streamPage.mapped, false); + }else{ + streamPage.init(pageName, bufferSize, false, true); + if (!streamPage.mapped){ + INFO_MSG("Page %s not found", pageName); + return; + } + stream = Util::RelAccX(streamPage.mapped, true); + } + } + + /// In master mode, creates and stores the fields for the "stream" child object. + /// In slave mode, simply class refresh(). + /// Regardless, afterwards the internal RelAccXFieldData members are updated with their correct + /// values. + void Meta::streamInit(size_t trackCount){ + if (isMaster){ + ///\todo Add safety for non-initialized stream object; + stream.addField("vod", RAX_UINT); + stream.addField("live", RAX_UINT); + stream.addField("tracks", RAX_NESTED, META_TRACK_OFFSET + (trackCount * META_TRACK_RECORDSIZE)); + stream.addField("source", RAX_STRING, 512); + stream.addField("bufferwindow", RAX_64UINT); + stream.addField("bootmsoffset", RAX_64INT); + stream.addField("minfragduration", RAX_64UINT); + stream.setRCount(1); + stream.setReady(); + stream.addRecords(1); + + trackList = Util::RelAccX(stream.getPointer("tracks"), false); + trackList.addField("valid", RAX_UINT); + trackList.addField("id", RAX_32UINT); + trackList.addField("type", RAX_32STRING); + trackList.addField("codec", RAX_32STRING); + trackList.addField("page", RAX_256STRING); + trackList.addField("lastupdate", RAX_64UINT); + trackList.addField("pid", RAX_32UINT); + trackList.addField("minkeepaway", RAX_64UINT); + trackList.addField("sourcetid", RAX_32UINT); + trackList.addField("encryption", RAX_256STRING); + trackList.addField("ivec", RAX_64UINT); + trackList.addField("widevine", RAX_256STRING); + trackList.addField("playready", RAX_STRING, 1024); + + trackList.setRCount(trackCount); + trackList.setReady(); + }else{ + refresh(); + } + // Initialize internal bufferFields + streamVodField = stream.getFieldData("vod"); + streamLiveField = stream.getFieldData("live"); + streamSourceField = stream.getFieldData("source"); + streamBufferWindowField = stream.getFieldData("bufferwindow"); + streamBootMsOffsetField = stream.getFieldData("bootmsoffset"); + streamMinimumFragmentDurationField = stream.getFieldData("minfragduration"); + + trackValidField = trackList.getFieldData("valid"); + trackIdField = trackList.getFieldData("id"); + trackTypeField = trackList.getFieldData("type"); + trackCodecField = trackList.getFieldData("codec"); + trackPageField = trackList.getFieldData("page"); + trackLastUpdateField = trackList.getFieldData("lastupdate"); + trackPidField = trackList.getFieldData("pid"); + trackMinKeepAwayField = trackList.getFieldData("minkeepaway"); + trackSourceTidField = trackList.getFieldData("sourcetid"); + trackEncryptionField = trackList.getFieldData("encryption"); + trackIvecField = trackList.getFieldData("ivec"); + trackWidevineField = trackList.getFieldData("widevine"); + trackPlayreadyField = trackList.getFieldData("playready"); + } + + /// Reads the "tracks" field from the "stream" child object, populating the "tracks" variable. + /// Does not clear "tracks" beforehand, so it may contain stale information afterwards if it was + /// already populated. + void Meta::refresh(){ + if (!stream.getPointer("tracks")){ + INFO_MSG("No track pointer, not refreshing."); + return; + } + trackList = Util::RelAccX(stream.getPointer("tracks"), false); + for (size_t i = 0; i < trackList.getPresent(); i++){ + if (trackList.getInt("valid", i) == 0){continue;} + if (tracks.count(i)){continue;} + IPC::sharedPage &p = tM[i]; + p.init(trackList.getPointer("page", i), SHM_STREAM_TRACK_LEN, false, false); + + Track &t = tracks[i]; + t.track = Util::RelAccX(p.mapped, true); + t.parts = Util::RelAccX(t.track.getPointer("parts"), true); + t.keys = Util::RelAccX(t.track.getPointer("keys"), true); + t.fragments = Util::RelAccX(t.track.getPointer("fragments"), true); + t.pages = Util::RelAccX(t.track.getPointer("pages"), true); + + t.trackIdField = t.track.getFieldData("id"); + t.trackTypeField = t.track.getFieldData("type"); + t.trackCodecField = t.track.getFieldData("codec"); + t.trackFirstmsField = t.track.getFieldData("firstms"); + t.trackLastmsField = t.track.getFieldData("lastms"); + t.trackBpsField = t.track.getFieldData("bps"); + t.trackMaxbpsField = t.track.getFieldData("maxbps"); + t.trackLangField = t.track.getFieldData("lang"); + t.trackInitField = t.track.getFieldData("init"); + t.trackRateField = t.track.getFieldData("rate"); + t.trackSizeField = t.track.getFieldData("size"); + t.trackChannelsField = t.track.getFieldData("channels"); + t.trackWidthField = t.track.getFieldData("width"); + t.trackHeightField = t.track.getFieldData("height"); + t.trackFpksField = t.track.getFieldData("fpks"); + t.trackMissedFragsField = t.track.getFieldData("missedFrags"); + + t.partSizeField = t.parts.getFieldData("size"); + t.partDurationField = t.parts.getFieldData("duration"); + t.partOffsetField = t.parts.getFieldData("offset"); + + t.keyFirstPartField = t.keys.getFieldData("firstpart"); + t.keyBposField = t.keys.getFieldData("bpos"); + t.keyDurationField = t.keys.getFieldData("duration"); + t.keyNumberField = t.keys.getFieldData("number"); + t.keyPartsField = t.keys.getFieldData("parts"); + t.keyTimeField = t.keys.getFieldData("time"); + t.keySizeField = t.keys.getFieldData("size"); + + t.fragmentDurationField = t.fragments.getFieldData("duration"); + t.fragmentKeysField = t.fragments.getFieldData("keys"); + t.fragmentFirstKeyField = t.fragments.getFieldData("firstkey"); + t.fragmentSizeField = t.fragments.getFieldData("size"); + } + } + + /// Merges in track information from a given DTSC::Meta object, optionally deleting missing tracks + /// and optionally making hard copies of the original data. + void Meta::merge(const DTSC::Meta &M, bool deleteTracks, bool copyData){ + std::set editedTracks; + std::set newTracks = M.getValidTracks(); + // Detect new tracks + for (std::set::iterator it = newTracks.begin(); it != newTracks.end(); it++){ + if (trackIDToIndex(M.getID(*it), getpid()) == INVALID_TRACK_ID){editedTracks.insert(*it);} + } + for (std::set::iterator it = editedTracks.begin(); it != editedTracks.end(); it++){ + size_t fragCount = DEFAULT_FRAGMENT_COUNT; + size_t keyCount = DEFAULT_KEY_COUNT; + size_t partCount = DEFAULT_PART_COUNT; + size_t pageCount = DEFAULT_PAGE_COUNT; + if (copyData){ + fragCount = M.tracks.at(*it).fragments.getRCount(); + keyCount = M.tracks.at(*it).keys.getRCount(); + partCount = M.tracks.at(*it).parts.getRCount(); + pageCount = M.tracks.at(*it).pages.getRCount(); + } + + size_t newIdx = addTrack(fragCount, keyCount, partCount, pageCount); + setInit(newIdx, M.getInit(*it)); + setID(newIdx, M.getID(*it)); + setChannels(newIdx, M.getChannels(*it)); + setRate(newIdx, M.getRate(*it)); + setWidth(newIdx, M.getWidth(*it)); + setHeight(newIdx, M.getHeight(*it)); + setSize(newIdx, M.getSize(*it)); + setType(newIdx, M.getType(*it)); + setCodec(newIdx, M.getCodec(*it)); + setLang(newIdx, M.getLang(*it)); + setFirstms(newIdx, M.getFirstms(*it)); + setLastms(newIdx, M.getLastms(*it)); + setBps(newIdx, M.getBps(*it)); + setMaxBps(newIdx, M.getMaxBps(*it)); + setFpks(newIdx, M.getFpks(*it)); + setMissedFragments(newIdx, M.getMissedFragments(*it)); + setMinKeepAway(newIdx, M.getMinKeepAway(*it)); + setSourceTrack(newIdx, M.getSourceTrack(*it)); + setEncryption(newIdx, M.getEncryption(*it)); + setPlayReady(newIdx, M.getPlayReady(*it)); + setWidevine(newIdx, M.getWidevine(*it)); + setIvec(newIdx, M.getIvec(*it)); + if (copyData){tracks[newIdx].track.flowFrom(M.tracks.at(*it).track);} + } + + if (deleteTracks){ + editedTracks.clear(); + std::set validTracks = getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (M.trackIDToIndex(getID(*it), getpid()) == INVALID_TRACK_ID){ + editedTracks.insert(*it); + } + } + for (std::set::iterator it = editedTracks.begin(); it != editedTracks.end(); it++){ + removeTrack(*it); + } + } + } + + /// Evaluates to true if this is a shared-memory-backed object, correctly mapped, with a non-exit + /// state on the "stream" RelAccX page. + Meta::operator bool() const{return !isMemBuf && streamPage.mapped && !stream.isExit();} + + /// Intended to be used for encryption. Not currently called anywhere. + size_t Meta::addCopy(size_t sourceTrack){ + if (isMemBuf){ + WARN_MSG("Unsupported operation for in-memory streams"); + return INVALID_TRACK_ID; + } + size_t tNumber = trackList.getPresent(); + + Track &t = tracks[tNumber]; + + char pageName[NAME_BUFFER_SIZE]; + snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_TM, streamName.c_str(), getpid(), tNumber); + INFO_MSG("Allocating page %s", pageName); + tM[tNumber].init(pageName, tM[sourceTrack].len, true); + tM[tNumber].master = false; + + memcpy(tM[tNumber].mapped, tM[sourceTrack].mapped, tM[sourceTrack].len); + t.track = Util::RelAccX(tM[tNumber].mapped, true); + + t.parts = Util::RelAccX(t.track.getPointer("parts"), true); + t.keys = Util::RelAccX(t.track.getPointer("keys"), true); + t.fragments = Util::RelAccX(t.track.getPointer("fragments"), true); + t.pages = Util::RelAccX(t.track.getPointer("pages"), true); + + trackList.addRecords(1); + trackList.setString(trackPageField, pageName, tNumber); + trackList.setInt(trackPidField, getpid(), tNumber); + trackList.setInt(trackSourceTidField, sourceTrack, tNumber); + validateTrack(tNumber); + return tNumber; + } + + /// Resizes a given track to be able to hold the given amount of fragments, keys, parts and pages. + /// Currently called exclusively from Meta::update(), to resize the internal structures. + void Meta::resizeTrack(size_t source, size_t fragCount, size_t keyCount, size_t partCount, size_t pageCount){ + INFO_MSG("Track %zu now with %zu frags, %zu keys, %zu parts, %zu pages", source, fragCount, + keyCount, partCount, pageCount); + size_t pageSize = (isMemBuf ? sizeMemBuf[source] : tM[source].len); + + char *orig = (char *)malloc(pageSize); + memcpy(orig, (isMemBuf ? tMemBuf[source] : tM[source].mapped), pageSize); + + Track &t = tracks[source]; + t.track.setReload(); + + size_t newPageSize = TRACK_TRACK_OFFSET + TRACK_TRACK_RECORDSIZE + + (TRACK_FRAGMENT_OFFSET + (TRACK_FRAGMENT_RECORDSIZE * fragCount)) + + (TRACK_KEY_OFFSET + (TRACK_KEY_RECORDSIZE * keyCount)) + + (TRACK_PART_OFFSET + (TRACK_PART_RECORDSIZE * partCount)) + + (TRACK_PAGE_OFFSET + (TRACK_PAGE_RECORDSIZE * pageCount)); + + if (isMemBuf){ + free(tMemBuf[source]); + tMemBuf.erase(source); + tMemBuf[source] = (char *)malloc(newPageSize); + sizeMemBuf[source] = newPageSize; + memset(tMemBuf[source], 0, newPageSize); + INFO_MSG("Done re-allocating buffer %zu", source); + + t.track = Util::RelAccX(tMemBuf[source], false); + }else{ + char pageName[NAME_BUFFER_SIZE]; + snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_TM, streamName.c_str(), getpid(), source); + INFO_MSG("Re-allocating page %s", pageName); + tM[source].master = true; + tM[source].init(pageName, newPageSize, true); + tM[source].master = false; + + t.track = Util::RelAccX(tM[source].mapped, false); + } + initializeTrack(t, fragCount, keyCount, partCount, pageCount); + + Util::RelAccX origAccess(orig); + + t.track.setInt(t.trackIdField, origAccess.getInt("id")); + t.track.setString(t.trackTypeField, origAccess.getPointer("type")); + t.track.setString(t.trackCodecField, origAccess.getPointer("codec")); + t.track.setInt(t.trackFirstmsField, origAccess.getInt("firstms")); + t.track.setInt(t.trackLastmsField, origAccess.getInt("lastms")); + t.track.setInt(t.trackBpsField, origAccess.getInt("bps")); + t.track.setInt(t.trackMaxbpsField, origAccess.getInt("maxbps")); + t.track.setString(t.trackLangField, origAccess.getPointer("lang")); + memcpy(t.track.getPointer(t.trackInitField), origAccess.getPointer("init"), 1024 * 1024); + t.track.setInt(t.trackRateField, origAccess.getInt("rate")); + t.track.setInt(t.trackSizeField, origAccess.getInt("size")); + t.track.setInt(t.trackChannelsField, origAccess.getInt("channels")); + t.track.setInt(t.trackWidthField, origAccess.getInt("width")); + t.track.setInt(t.trackHeightField, origAccess.getInt("height")); + t.track.setInt(t.trackFpksField, origAccess.getInt("fpks")); + t.track.setInt(t.trackMissedFragsField, origAccess.getInt("missedFrags")); + + Util::RelAccX origParts(origAccess.getPointer("parts")); + t.parts.deleteRecords(origParts.getDeleted()); + + Util::FieldAccX origPartSizeAccX = origParts.getFieldAccX("size"); + Util::FieldAccX origPartDurationAccX = origParts.getFieldAccX("duration"); + Util::FieldAccX origPartOffsetAccX = origParts.getFieldAccX("offset"); + + Util::FieldAccX partSizeAccX = t.parts.getFieldAccX("size"); + Util::FieldAccX partDurationAccX = t.parts.getFieldAccX("duration"); + Util::FieldAccX partOffsetAccX = t.parts.getFieldAccX("offset"); + + size_t firstPart = origParts.getStartPos(); + size_t endPart = origParts.getEndPos(); + for (size_t i = firstPart; i < endPart; i++){ + partSizeAccX.set(origPartSizeAccX.uint(i), i); + partDurationAccX.set(origPartDurationAccX.uint(i), i); + partOffsetAccX.set(origPartOffsetAccX.uint(i), i); + } + t.parts.addRecords(origParts.getPresent()); + + Util::RelAccX origKeys(origAccess.getPointer("keys")); + t.keys.deleteRecords(origKeys.getDeleted()); + + Util::FieldAccX origKeyFirstpartAccX = origKeys.getFieldAccX("firstpart"); + Util::FieldAccX origKeyBposAccX = origKeys.getFieldAccX("bpos"); + Util::FieldAccX origKeyDurationAccX = origKeys.getFieldAccX("duration"); + Util::FieldAccX origKeyNumberAccX = origKeys.getFieldAccX("number"); + Util::FieldAccX origKeyPartsAccX = origKeys.getFieldAccX("parts"); + Util::FieldAccX origKeyTimeAccX = origKeys.getFieldAccX("time"); + Util::FieldAccX origKeySizeAccX = origKeys.getFieldAccX("size"); + + Util::FieldAccX keyFirstpartAccX = t.keys.getFieldAccX("firstpart"); + Util::FieldAccX keyBposAccX = t.keys.getFieldAccX("bpos"); + Util::FieldAccX keyDurationAccX = t.keys.getFieldAccX("duration"); + Util::FieldAccX keyNumberAccX = t.keys.getFieldAccX("number"); + Util::FieldAccX keyPartsAccX = t.keys.getFieldAccX("parts"); + Util::FieldAccX keyTimeAccX = t.keys.getFieldAccX("time"); + Util::FieldAccX keySizeAccX = t.keys.getFieldAccX("size"); + + size_t firstKey = origKeys.getStartPos(); + size_t endKey = origKeys.getEndPos(); + for (size_t i = firstKey; i < endKey; i++){ + keyFirstpartAccX.set(origKeyFirstpartAccX.uint(i), i); + keyBposAccX.set(origKeyBposAccX.uint(i), i); + keyDurationAccX.set(origKeyDurationAccX.uint(i), i); + keyNumberAccX.set(origKeyNumberAccX.uint(i), i); + keyPartsAccX.set(origKeyPartsAccX.uint(i), i); + keyTimeAccX.set(origKeyTimeAccX.uint(i), i); + keySizeAccX.set(origKeySizeAccX.uint(i), i); + } + t.keys.addRecords(origKeys.getPresent()); + + Util::RelAccX origFragments(origAccess.getPointer("fragments")); + t.fragments.deleteRecords(origFragments.getDeleted()); + + Util::FieldAccX origFragmentDurationAccX = origFragments.getFieldAccX("duration"); + Util::FieldAccX origFragmentKeysAccX = origFragments.getFieldAccX("keys"); + Util::FieldAccX origFragmentFirstkeyAccX = origFragments.getFieldAccX("firstkey"); + Util::FieldAccX origFragmentSizeAccX = origFragments.getFieldAccX("size"); + + Util::FieldAccX fragmentDurationAccX = t.fragments.getFieldAccX("duration"); + Util::FieldAccX fragmentKeysAccX = t.fragments.getFieldAccX("keys"); + Util::FieldAccX fragmentFirstkeyAccX = t.fragments.getFieldAccX("firstkey"); + Util::FieldAccX fragmentSizeAccX = t.fragments.getFieldAccX("size"); + + size_t firstFragment = origFragments.getStartPos(); + size_t endFragment = origFragments.getEndPos(); + for (size_t i = firstFragment; i < endFragment; i++){ + fragmentDurationAccX.set(origFragmentDurationAccX.uint(i), i); + fragmentKeysAccX.set(origFragmentKeysAccX.uint(i), i); + fragmentFirstkeyAccX.set(origFragmentFirstkeyAccX.uint(i), i); + fragmentSizeAccX.set(origFragmentSizeAccX.uint(i), i); + } + t.fragments.addRecords(origFragments.getPresent()); + + Util::RelAccX origPages(origAccess.getPointer("pages")); + t.pages.deleteRecords(origPages.getDeleted()); + + Util::FieldAccX origPageFirstkeyAccX = origPages.getFieldAccX("firstkey"); + Util::FieldAccX origPageKeycountAccX = origPages.getFieldAccX("keycount"); + Util::FieldAccX origPagePartsAccX = origPages.getFieldAccX("parts"); + Util::FieldAccX origPageSizeAccX = origPages.getFieldAccX("size"); + Util::FieldAccX origPageAvailAccX = origPages.getFieldAccX("avail"); + Util::FieldAccX origPageFirsttimeAccX = origPages.getFieldAccX("firsttime"); + Util::FieldAccX origPageLastkeytimeAccX = origPages.getFieldAccX("lastkeytime"); + + Util::FieldAccX pageFirstkeyAccX = t.pages.getFieldAccX("firstkey"); + Util::FieldAccX pageKeycountAccX = t.pages.getFieldAccX("keycount"); + Util::FieldAccX pagePartsAccX = t.pages.getFieldAccX("parts"); + Util::FieldAccX pageSizeAccX = t.pages.getFieldAccX("size"); + Util::FieldAccX pageAvailAccX = t.pages.getFieldAccX("avail"); + Util::FieldAccX pageFirsttimeAccX = t.pages.getFieldAccX("firsttime"); + Util::FieldAccX pageLastkeytimeAccX = t.pages.getFieldAccX("lastkeytime"); + + size_t firstPage = origPages.getStartPos(); + size_t endPage = origPages.getEndPos(); + for (size_t i = firstPage; i < endPage; i++){ + pageFirstkeyAccX.set(origPageFirstkeyAccX.uint(i), i); + pageKeycountAccX.set(origPageKeycountAccX.uint(i), i); + pagePartsAccX.set(origPagePartsAccX.uint(i), i); + pageSizeAccX.set(origPageSizeAccX.uint(i), i); + pageAvailAccX.set(origPageAvailAccX.uint(i), i); + pageFirsttimeAccX.set(origPageFirsttimeAccX.uint(i), i); + pageLastkeytimeAccX.set(origPageLastkeytimeAccX.uint(i), i); + } + t.pages.addRecords(origPages.getPresent()); + + free(orig); + } + + size_t Meta::addDelayedTrack(size_t fragCount, size_t keyCount, size_t partCount, size_t pageCount){ + return addTrack(fragCount, keyCount, partCount, pageCount, false); + } + + /// Adds a track to the metadata structure. + /// To be called from the various inputs/outputs whenever they want to add a track. + size_t Meta::addTrack(size_t fragCount, size_t keyCount, size_t partCount, size_t pageCount, bool setValid){ + size_t pageSize = TRACK_TRACK_OFFSET + TRACK_TRACK_RECORDSIZE + + (TRACK_FRAGMENT_OFFSET + (TRACK_FRAGMENT_RECORDSIZE * fragCount)) + + (TRACK_KEY_OFFSET + (TRACK_KEY_RECORDSIZE * keyCount)) + + (TRACK_PART_OFFSET + (TRACK_PART_RECORDSIZE * partCount)) + + (TRACK_PAGE_OFFSET + (TRACK_PAGE_RECORDSIZE * pageCount)); + + size_t tNumber = trackList.getPresent(); + + char pageName[NAME_BUFFER_SIZE]; + snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_TM, streamName.c_str(), getpid(), tNumber); + + Track &t = tracks[tNumber]; + + if (isMemBuf){ + tMemBuf[tNumber] = (char *)malloc(pageSize); + sizeMemBuf[tNumber] = pageSize; + memset(tMemBuf[tNumber], 0, pageSize); + + t.track = Util::RelAccX(tMemBuf[tNumber], false); + }else{ + tM[tNumber].init(pageName, pageSize, true); + tM[tNumber].master = false; + + t.track = Util::RelAccX(tM[tNumber].mapped, false); + } + initializeTrack(t, fragCount, keyCount, partCount, pageCount); + trackList.addRecords(1); + trackList.setString(trackPageField, pageName, tNumber); + trackList.setInt(trackPidField, getpid(), tNumber); + trackList.setInt(trackSourceTidField, INVALID_TRACK_ID, tNumber); + if (setValid){validateTrack(tNumber);} + + return tNumber; + } + + /// Internal function that is called whenever a track is (re)written to the memory structures. + /// Adds the needed fields and sets all the RelAccXFieldData members to point to them. + void Meta::initializeTrack(Track &t, size_t fragCount, size_t keyCount, size_t partCount, size_t pageCount){ + t.track.addField("id", RAX_32UINT); + t.track.addField("type", RAX_STRING, 8); + t.track.addField("codec", RAX_STRING, 8); + t.track.addField("firstms", RAX_64UINT); + t.track.addField("lastms", RAX_64UINT); + t.track.addField("bps", RAX_32UINT); + t.track.addField("maxbps", RAX_32UINT); + t.track.addField("lang", RAX_STRING, 4); + t.track.addField("init", RAX_RAW, 1 * 1024 * 1024); // 1megabyte init data + t.track.addField("rate", RAX_16UINT); + t.track.addField("size", RAX_16UINT); + t.track.addField("channels", RAX_16UINT); + t.track.addField("width", RAX_32UINT); + t.track.addField("height", RAX_32UINT); + t.track.addField("parts", RAX_NESTED, TRACK_PART_OFFSET + (TRACK_PART_RECORDSIZE * partCount)); + t.track.addField("keys", RAX_NESTED, TRACK_KEY_OFFSET + (TRACK_KEY_RECORDSIZE * keyCount)); + t.track.addField("fragments", RAX_NESTED, TRACK_FRAGMENT_OFFSET + (TRACK_FRAGMENT_RECORDSIZE * fragCount)); + t.track.addField("pages", RAX_NESTED, TRACK_PAGE_OFFSET + (TRACK_PAGE_RECORDSIZE * pageCount)); + t.track.addField("fpks", RAX_16UINT); + t.track.addField("missedFrags", RAX_32UINT); + + t.track.setRCount(1); + t.track.setReady(); + t.track.addRecords(1); + + t.parts = Util::RelAccX(t.track.getPointer("parts"), false); + t.parts.addField("size", RAX_32UINT); + t.parts.addField("duration", RAX_16UINT); + t.parts.addField("offset", RAX_16INT); + t.parts.setRCount(partCount); + t.parts.setReady(); + + t.keys = Util::RelAccX(t.track.getPointer("keys"), false); + t.keys.addField("firstpart", RAX_64UINT); + t.keys.addField("bpos", RAX_64UINT); + t.keys.addField("duration", RAX_32UINT); + t.keys.addField("number", RAX_32UINT); + t.keys.addField("parts", RAX_32UINT); + t.keys.addField("time", RAX_64UINT); + t.keys.addField("size", RAX_32UINT); + t.keys.setRCount(keyCount); + t.keys.setReady(); + + t.fragments = Util::RelAccX(t.track.getPointer("fragments"), false); + t.fragments.addField("duration", RAX_32UINT); + t.fragments.addField("keys", RAX_16UINT); + t.fragments.addField("firstkey", RAX_32UINT); + t.fragments.addField("size", RAX_32UINT); + t.fragments.setRCount(fragCount); + t.fragments.setReady(); + + t.trackIdField = t.track.getFieldData("id"); + t.trackTypeField = t.track.getFieldData("type"); + t.trackCodecField = t.track.getFieldData("codec"); + t.trackFirstmsField = t.track.getFieldData("firstms"); + t.trackLastmsField = t.track.getFieldData("lastms"); + t.trackBpsField = t.track.getFieldData("bps"); + t.trackMaxbpsField = t.track.getFieldData("maxbps"); + t.trackLangField = t.track.getFieldData("lang"); + t.trackInitField = t.track.getFieldData("init"); + t.trackRateField = t.track.getFieldData("rate"); + t.trackSizeField = t.track.getFieldData("size"); + t.trackChannelsField = t.track.getFieldData("channels"); + t.trackWidthField = t.track.getFieldData("width"); + t.trackHeightField = t.track.getFieldData("height"); + t.trackFpksField = t.track.getFieldData("fpks"); + t.trackMissedFragsField = t.track.getFieldData("missedFrags"); + + t.partSizeField = t.parts.getFieldData("size"); + t.partDurationField = t.parts.getFieldData("duration"); + t.partOffsetField = t.parts.getFieldData("offset"); + + t.keyFirstPartField = t.keys.getFieldData("firstpart"); + t.keyBposField = t.keys.getFieldData("bpos"); + t.keyDurationField = t.keys.getFieldData("duration"); + t.keyNumberField = t.keys.getFieldData("number"); + t.keyPartsField = t.keys.getFieldData("parts"); + t.keyTimeField = t.keys.getFieldData("time"); + t.keySizeField = t.keys.getFieldData("size"); + + t.fragmentDurationField = t.fragments.getFieldData("duration"); + t.fragmentKeysField = t.fragments.getFieldData("keys"); + t.fragmentFirstKeyField = t.fragments.getFieldData("firstkey"); + t.fragmentSizeField = t.fragments.getFieldData("size"); + + t.pages = Util::RelAccX(t.track.getPointer("pages"), false); + t.pages.addField("firstkey", RAX_32UINT); + t.pages.addField("keycount", RAX_32UINT); + t.pages.addField("parts", RAX_32UINT); + t.pages.addField("size", RAX_32UINT); + t.pages.addField("avail", RAX_32UINT); + t.pages.addField("firsttime", RAX_64UINT); + t.pages.addField("lastkeytime", RAX_64UINT); + t.pages.setRCount(pageCount); + t.pages.setReady(); + } + + /// Sets the given track's init data. + /// Simply calls setInit(size_t, const char *, size_t) using values from the referenced + /// std::string. + void Meta::setInit(size_t trackIdx, const std::string &init){ + setInit(trackIdx, init.data(), init.size()); + } + + /// Sets the given track's init data. + void Meta::setInit(size_t trackIdx, const char *init, size_t initLen){ + DTSC::Track &t = tracks.at(trackIdx); + char *_init = t.track.getPointer(t.trackInitField); + Bit::htobs(_init, initLen); + memcpy(_init + 2, init, initLen); + } + + /// Retrieves the given track's init data as std::string. + std::string Meta::getInit(size_t idx) const{ + const DTSC::Track &t = tracks.at(idx); + char *src = t.track.getPointer(t.trackInitField); + uint16_t size = Bit::btohs(src); + return std::string(src + 2, size); + } + + void Meta::setSource(const std::string &src){stream.setString(streamSourceField, src);} + std::string Meta::getSource() const{return stream.getPointer(streamSourceField);} + + void Meta::setID(size_t trackIdx, size_t id){ + trackList.setInt(trackIdField, id, trackIdx); + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackIdField, id); + } + size_t Meta::getID(size_t trackIdx) const{return trackList.getInt(trackIdField, trackIdx);} + + /// Writes Util::bootSecs() to the track's last updated field. + void Meta::markUpdated(size_t trackIdx){ + trackList.setInt(trackLastUpdateField, Util::bootSecs(), trackIdx); + } + + /// Reads the track's last updated field, which should be the Util::bootSecs() value of the time + /// of last update. + uint64_t Meta::getLastUpdated(size_t trackIdx) const{ + return trackList.getInt(trackLastUpdateField, trackIdx); + } + + /// Reads the most recently updated track last updated field, which should be the Util::bootSecs() + /// value of the time of last update. + uint64_t Meta::getLastUpdated() const{ + uint64_t ret = 0; + std::set validTracks = getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + uint64_t trackUp = getLastUpdated(*it); + if (trackUp > ret){ret = trackUp;} + } + return ret; + } + + void Meta::setChannels(size_t trackIdx, uint16_t channels){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackChannelsField, channels); + } + uint16_t Meta::getChannels(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackChannelsField); + } + + void Meta::setWidth(size_t trackIdx, uint32_t width){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackWidthField, width); + } + uint32_t Meta::getWidth(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackWidthField); + } + + void Meta::setHeight(size_t trackIdx, uint32_t height){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackHeightField, height); + } + uint32_t Meta::getHeight(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackHeightField); + } + + void Meta::setRate(size_t trackIdx, uint32_t rate){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackRateField, rate); + } + uint32_t Meta::getRate(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackRateField); + } + + void Meta::setSize(size_t trackIdx, uint16_t size){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackSizeField, size); + } + uint16_t Meta::getSize(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackSizeField); + } + + void Meta::setType(size_t trackIdx, const std::string &type){ + trackList.setString(trackTypeField, type, trackIdx); + DTSC::Track &t = tracks.at(trackIdx); + t.track.setString(t.trackTypeField, type); + } + std::string Meta::getType(size_t trackIdx) const{ + return trackList.getPointer(trackTypeField, trackIdx); + } + + void Meta::setCodec(size_t trackIdx, const std::string &codec){ + trackList.setString(trackCodecField, codec, trackIdx); + DTSC::Track &t = tracks.at(trackIdx); + t.track.setString(t.trackCodecField, codec); + } + std::string Meta::getCodec(size_t trackIdx) const{ + return trackList.getPointer(trackCodecField, trackIdx); + } + + void Meta::setLang(size_t trackIdx, const std::string &lang){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setString(t.trackLangField, lang); + } + std::string Meta::getLang(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getPointer(t.trackLangField); + } + + void Meta::setFirstms(size_t trackIdx, uint64_t firstms){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackFirstmsField, firstms); + } + uint64_t Meta::getFirstms(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackFirstmsField); + } + + void Meta::setLastms(size_t trackIdx, uint64_t lastms){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackLastmsField, lastms); + } + uint64_t Meta::getLastms(size_t trackIdx) const{ + const DTSC::Track &t = tracks.find(trackIdx)->second; + return t.track.getInt(t.trackLastmsField); + } + + uint64_t Meta::getDuration(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackLastmsField) - t.track.getInt(t.trackFirstmsField); + } + + void Meta::setBps(size_t trackIdx, uint64_t bps){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackBpsField, bps); + } + uint64_t Meta::getBps(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackBpsField); + } + + void Meta::setMaxBps(size_t trackIdx, uint64_t bps){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackMaxbpsField, bps); + } + uint64_t Meta::getMaxBps(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackMaxbpsField); + } + + void Meta::setFpks(size_t trackIdx, uint64_t bps){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackFpksField, bps); + } + uint64_t Meta::getFpks(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackFpksField); + } + + void Meta::setMissedFragments(size_t trackIdx, uint32_t bps){ + DTSC::Track &t = tracks.at(trackIdx); + t.track.setInt(t.trackMissedFragsField, bps); + } + uint32_t Meta::getMissedFragments(size_t trackIdx) const{ + const DTSC::Track &t = tracks.at(trackIdx); + return t.track.getInt(t.trackMissedFragsField); + } + + void Meta::setMinKeepAway(size_t trackIdx, uint64_t minKeepAway){ + trackList.setInt(trackMinKeepAwayField, minKeepAway); + } + + uint64_t Meta::getMinKeepAway(size_t trackIdx) const{ + return trackList.getInt(trackMinKeepAwayField); + } + + void Meta::setEncryption(size_t trackIdx, const std::string &encryption){ + trackList.setString(trackEncryptionField, encryption, trackIdx); + } + std::string Meta::getEncryption(size_t trackIdx) const{ + return trackList.getPointer(trackEncryptionField, trackIdx); + } + + void Meta::setWidevine(size_t trackIdx, const std::string &widevine){ + trackList.setString(trackWidevineField, widevine, trackIdx); + } + std::string Meta::getWidevine(size_t trackIdx) const{ + return trackList.getPointer(trackWidevineField, trackIdx); + } + + void Meta::setPlayReady(size_t trackIdx, const std::string &playReady){ + trackList.setString(trackPlayreadyField, playReady, trackIdx); + } + std::string Meta::getPlayReady(size_t trackIdx) const{ + return trackList.getPointer(trackPlayreadyField, trackIdx); + } + + void Meta::setIvec(size_t trackIdx, uint64_t ivec){ + trackList.setInt(trackIvecField, ivec, trackIdx); + } + uint64_t Meta::getIvec(size_t trackIdx) const{ + return trackList.getInt(trackIvecField, trackIdx); + } + + void Meta::setSourceTrack(size_t trackIdx, size_t sourceTrack){ + trackList.setInt(trackSourceTidField, sourceTrack, trackIdx); + } + uint64_t Meta::getSourceTrack(size_t trackIdx) const{ + return trackList.getInt(trackSourceTidField, trackIdx); + } + + void Meta::setVod(bool vod){ + stream.setInt(streamVodField, vod ? 1 : 0); + stream.setInt(streamLiveField, vod ? 0 : 1); + } + bool Meta::getVod() const{return stream.getInt(streamVodField);} + + void Meta::setLive(bool live){ + stream.setInt(streamLiveField, live ? 1 : 0); + stream.setInt(streamVodField, live ? 0 : 1); + } + bool Meta::getLive() const{return stream.getInt(streamLiveField);} + + bool Meta::hasBFrames(size_t idx) const{ + std::set vTracks = getValidTracks(); + for (std::set::iterator it = vTracks.begin(); it != vTracks.end(); it++){ + if (idx != INVALID_TRACK_ID && idx != *it){continue;} + if (getType(*it) != "video"){continue;} + DTSC::Parts p(parts(*it)); + size_t ctr = 0; + for (size_t i = p.getFirstValid(); i < p.getEndValid(); ++i){ + if (p.getOffset(i)){return true;} + if (++ctr >= 100){break;} + } + } return false; } - headerSize = header.size(); - int pSize = htonl(header.size()); - fseek(F, 4, SEEK_SET); - int tmpret = fwrite((void *)(&pSize), 4, 1, F); - if (tmpret != 1){return false;} - fseek(F, 8, SEEK_SET); - int ret = fwrite(header.c_str(), headerSize, 1, F); - fseek(F, 8 + headerSize, SEEK_SET); - return (ret == 1); -} -/// Adds the given string as a new header to the end of the file. -/// \returns The positon the header was written at, or 0 on failure. -long long int DTSC::File::addHeader(std::string &header){ - fseek(F, 0, SEEK_END); - long long int writePos = ftell(F); - int hSize = htonl(header.size()); - int ret = fwrite(DTSC::Magic_Header, 4, 1, F); // write header - if (ret != 1){return 0;} - ret = fwrite((void *)(&hSize), 4, 1, F); // write size - if (ret != 1){return 0;} - ret = fwrite(header.c_str(), header.size(), 1, F); // write contents - if (ret != 1){return 0;} - fseek(F, 0, SEEK_END); - endPos = ftell(F); - return writePos; // return position written at -} + void Meta::setBufferWindow(uint64_t bufferWindow){ + stream.setInt(streamBufferWindowField, bufferWindow); + } + uint64_t Meta::getBufferWindow() const{return stream.getInt(streamBufferWindowField);} -/// Reads the header at the given file position. -/// If the packet could not be read for any reason, the reason is printed. -/// Reading the header means the file position is moved to after the header. -void DTSC::File::readHeader(int pos){ - fseek(F, pos, SEEK_SET); - if (fread(buffer, 4, 1, F) != 1){ - if (feof(F)){ - DEBUG_MSG(DLVL_DEVEL, "End of file reached while reading header @ %d", pos); - }else{ - DEBUG_MSG(DLVL_ERROR, "Could not read header @ %d", pos); + void Meta::setBootMsOffset(uint64_t bootMsOffset){ + stream.setInt(streamBootMsOffsetField, bootMsOffset); + } + uint64_t Meta::getBootMsOffset() const{return stream.getInt(streamBootMsOffsetField);} + /*LTS-START*/ + void Meta::setMinimumFragmentDuration(uint64_t fragmentDuration){ + stream.setInt(streamMinimumFragmentDurationField, fragmentDuration); + } + uint64_t Meta::getMinimumFragmentDuration() const{ + uint64_t res = stream.getInt(streamMinimumFragmentDurationField); + if (res > 0){return res;} + return DEFAULT_FRAGMENT_DURATION; + } + /*LTS-END*/ + + std::set Meta::getValidTracks(bool skipEmpty) const{ + std::set res; + if (!(*this) && !isMemBuf){return res;} + uint64_t firstValid = trackList.getDeleted(); + uint64_t beyondLast = firstValid + trackList.getPresent(); + for (size_t i = firstValid; i < beyondLast; i++){ + if (trackList.getInt(trackValidField, i) == 1){res.insert(i);} + if (trackList.getInt(trackSourceTidField, i) != INVALID_TRACK_ID && + std::string(trackList.getPointer(trackEncryptionField, i)) != ""){ + res.erase(trackList.getInt(trackSourceTidField, i)); + } + if (!tracks.count(i)){res.erase(i);} + if (skipEmpty){ + if (res.count(i) && !tracks.at(i).parts.getPresent()){res.erase(i);} + } } - metadata = Meta(); - return; + return res; } - if (memcmp(buffer, DTSC::Magic_Header, 4) != 0){ - DEBUG_MSG(DLVL_ERROR, "Invalid header - %.4s != %.4s @ %i", (char *)buffer, DTSC::Magic_Header, pos); - metadata = Meta(); - return; - } - if (fread(buffer, 4, 1, F) != 1){ - DEBUG_MSG(DLVL_ERROR, "Could not read header size @ %i", pos); - metadata = Meta(); - return; - } - long packSize = ntohl(((unsigned long *)buffer)[0]) + 8; - std::string strBuffer; - strBuffer.resize(packSize); - if (packSize){ - fseek(F, pos, SEEK_SET); - if (fread((void *)strBuffer.c_str(), packSize, 1, F) != 1){ - DEBUG_MSG(DLVL_ERROR, "Could not read header packet @ %i", pos); - metadata = Meta(); - return; + + std::set Meta::getMySourceTracks(size_t pid) const{ + std::set res; + if (!streamPage.mapped){return res;} + uint64_t firstValid = trackList.getDeleted(); + uint64_t beyondLast = firstValid + trackList.getPresent(); + for (size_t i = firstValid; i < beyondLast; i++){ + if (trackList.getInt(trackValidField, i) == 1 && trackList.getInt(trackPidField, i) == pid){ + res.insert(i); + } } - metadata = Meta(DTSC::Packet(strBuffer.data(), strBuffer.size(), true)); + return res; } - // if there is another header, read it and replace metadata with that one. - if (metadata.moreheader){ - if (metadata.moreheader < getBytePosEOF()){ - readHeader(metadata.moreheader); - return; + + /// Sets the track valid field to 1, also calling markUpdated() + void Meta::validateTrack(size_t trackIdx){ + markUpdated(trackIdx); + trackList.setInt(trackValidField, 1, trackIdx); + } + + void Meta::removeEmptyTracks(){ + refresh(); + std::set validTracks = getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (!tracks.at(*it).parts.getPresent()){removeTrack(*it);} } } - if (!metadata.live){metadata.vod = true;} -} -long int DTSC::File::getBytePosEOF(){ - return endPos; -} - -long int DTSC::File::getBytePos(){ - return ftell(F); -} - -bool DTSC::File::reachedEOF(){ - return feof(F); -} - -/// Reads the packet available at the current file position. -/// If the packet could not be read for any reason, the reason is printed. -/// Reading the packet means the file position is increased to the next packet. -void DTSC::File::seekNext(){ - if (!currentPositions.size()){ - DEBUG_MSG(DLVL_WARN, "No seek positions set - returning empty packet."); - myPack.null(); - return; - } - seekPos thisPos = *currentPositions.begin(); - fseek(F, thisPos.bytePos, SEEK_SET); - if (reachedEOF()){ - myPack.null(); - return; - } - clearerr(F); - currentPositions.erase(currentPositions.begin()); - lastreadpos = ftell(F); - if (fread(buffer, 4, 1, F) != 1){ - if (feof(F)){ - DEBUG_MSG(DLVL_DEVEL, "End of file reached while seeking @ %i", (int)lastreadpos); - }else{ - DEBUG_MSG(DLVL_ERROR, "Could not seek to next @ %i", (int)lastreadpos); + /// Removes the track from the memory structure and caches. + void Meta::removeTrack(size_t trackIdx){ + if (!getValidTracks().count(trackIdx)){return;} + Track &t = tracks[trackIdx]; + for (uint64_t i = t.pages.getDeleted(); i < t.pages.getEndPos(); i++){ + if (t.pages.getInt("avail", i) == 0){continue;} + char thisPageName[NAME_BUFFER_SIZE]; + snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackIdx, + t.pages.getInt("firstkey", i)); + IPC::sharedPage p(thisPageName, 20971520); + p.master = true; } - myPack.null(); - return; + tM[trackIdx].master = true; + tM.erase(trackIdx); + tracks.erase(trackIdx); + + trackList.setInt(trackValidField, 0, trackIdx); } - if (memcmp(buffer, DTSC::Magic_Header, 4) == 0){ - seek_time(myPack.getTime(), myPack.getTrackId(), true); - return seekNext(); + + /// Removes the first key from the memory structure and caches. + void Meta::removeFirstKey(size_t trackIdx){ + Track &t = tracks[trackIdx]; + t.parts.deleteRecords(t.keys.getInt(t.keyPartsField, t.keys.getDeleted())); + t.keys.deleteRecords(1); + if (t.fragments.getInt(t.fragmentFirstKeyField, t.fragments.getDeleted()) < t.keys.getDeleted()){ + t.fragments.deleteRecords(1); + setMissedFragments(trackIdx, getMissedFragments(trackIdx) + 1); + } + if (t.pages.getPresent() > 1 && t.pages.getInt("firstkey", t.pages.getDeleted() + 1) < t.keys.getDeleted()){ + // Initialize the correct page, make it master so it gets cleaned up when leaving scope. + char thisPageName[NAME_BUFFER_SIZE]; + snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackIdx, + t.pages.getInt("firstkey", t.pages.getDeleted())); + IPC::sharedPage p(thisPageName, 20971520); + p.master = true; + + // Then delete the page entry + t.pages.deleteRecords(1); + } + setFirstms(trackIdx, t.keys.getInt(t.keyTimeField, t.keys.getDeleted())); } - long long unsigned int 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){ - DEBUG_MSG(DLVL_ERROR, "Invalid packet header @ %#x - %.4s != %.4s @ %d", - (unsigned int)lastreadpos, (char *)buffer, DTSC::Magic_Packet2, (int)lastreadpos); - myPack.null(); - return; + + ///\brief Updates a meta object given a DTSC::Packet with byte position override. + void Meta::updatePosOverride(DTSC::Packet &pack, uint64_t bpos){ + char *data; + size_t dataLen; + pack.getString("data", data, dataLen); + update(pack.getTime(), pack.getInt("offset"), pack.getTrackId(), dataLen, bpos, + pack.getFlag("keyframe"), pack.getDataLen()); } - if (fread(buffer, 4, 1, F) != 1){ - DEBUG_MSG(DLVL_ERROR, "Could not read packet size @ %d", (int)lastreadpos); - myPack.null(); - return; + + ///\brief Updates a meta object given a DTSC::Packet + void Meta::update(const DTSC::Packet &pack){ + char *data; + size_t dataLen; + pack.getString("data", data, dataLen); + update(pack.getTime(), pack.getInt("offset"), pack.getTrackId(), dataLen, pack.getInt("bpos"), + pack.getFlag("keyframe"), pack.getDataLen()); } - long packSize = ntohl(((unsigned long *)buffer)[0]); - char *packBuffer = (char *)malloc(packSize + 8); - if (version == 1){ - memcpy(packBuffer, "DTPD", 4); - }else{ - memcpy(packBuffer, "DTP2", 4); - } - memcpy(packBuffer + 4, buffer, 4); - if (fread((void *)(packBuffer + 8), packSize, 1, F) != 1){ - DEBUG_MSG(DLVL_ERROR, "Could not read packet @ %d", (int)lastreadpos); - myPack.null(); - free(packBuffer); - return; - } - myPack.reInit(packBuffer, packSize + 8); - free(packBuffer); - if (metadata.merged){ - int tempLoc = getBytePos(); - char newHeader[20]; - bool insert = false; - seekPos tmpPos; - if (fread((void *)newHeader, 20, 1, F) == 1){ - if (memcmp(newHeader, DTSC::Magic_Packet2, 4) == 0){ - tmpPos.bytePos = tempLoc; - tmpPos.trackID = ntohl(((int *)newHeader)[2]); - tmpPos.seekTime = 0; - if (selectedTracks.find(tmpPos.trackID) != selectedTracks.end()){ - tmpPos.seekTime = ((long long unsigned int)ntohl(((int *)newHeader)[3])) << 32; - tmpPos.seekTime += ntohl(((int *)newHeader)[4]); - insert = true; - }else{ - long tid = myPack.getTrackId(); - for (unsigned int i = 0; i != metadata.tracks[tid].keys.size(); i++){ - if ((unsigned long long)metadata.tracks[tid].keys[i].getTime() > myPack.getTime()){ - tmpPos.seekTime = metadata.tracks[tid].keys[i].getTime(); - tmpPos.bytePos = metadata.tracks[tid].keys[i].getBpos(); - tmpPos.trackID = tid; - insert = true; - break; - } - } + + /// Helper class that calculates inter-packet jitter + class jitterTimer{ + public: + uint64_t trueTime[8]; // Array of bootMS-based measurement points + uint64_t packTime[8]; // Array of corresponding packet times + uint64_t curJitter; // Maximum jitter measurement in past 10 seconds + unsigned int x; // Current indice within above two arrays + uint64_t maxJitter; // Highest jitter ever observed by this jitterTimer + uint64_t lastTime; // Last packet used for a measurement point + jitterTimer(){ + for (int i = 0; i < 8; ++i){ + trueTime[i] = 0; + packTime[i] = 0; + } + maxJitter = 200; + lastTime = 0; + x = 0; + } + uint64_t addPack(uint64_t t){ + if (veryUglyJitterOverride){return veryUglyJitterOverride;} + uint64_t curMs = Util::bootMS(); + if (!x){ + // First call, set the whole array to this packet + for (int i = 0; i < 8; ++i){ + trueTime[i] = curMs; + packTime[i] = t; } - if (currentPositions.size()){ - for (std::set::iterator curPosIter = currentPositions.begin(); - curPosIter != currentPositions.end(); curPosIter++){ - if ((*curPosIter).trackID == tmpPos.trackID && (*curPosIter).seekTime >= tmpPos.seekTime){ - insert = false; - break; - } + ++x; + trueTime[x % 8] = curMs; + packTime[x % 8] = t; + lastTime = t; + curJitter = 0; + } + if (t > lastTime + 2500){ + if ((x % 4) == 0 && maxJitter > 50 && curJitter < maxJitter - 50){ + HIGH_MSG("Jitter lowered from %" PRIu64 " to %" PRIu64 " ms", maxJitter, curJitter); + maxJitter = curJitter; + curJitter = 0; + } + ++x; + trueTime[x % 8] = curMs; + packTime[x % 8] = t; + lastTime = t; + } + uint64_t realTime = (curMs - trueTime[(x + 1) % 8]); + uint64_t arriTime = (t - packTime[(x + 1) % 8]); + int64_t jitter = (realTime - arriTime); + if (jitter < 0){ + // Negative jitter = packets arriving too soon. + // This is... ehh... not a bad thing? I guess..? + // if (jitter < -1000){ + // INFO_MSG("Jitter = %" PRId64 " ms (max: %" PRIu64 ")", jitter, maxJitter); + //} + }else{ + // Postive jitter = packets arriving too late. + // We need to delay playback at least by this amount to account for it. + if ((uint64_t)jitter > maxJitter){ + HIGH_MSG("Jitter increased from %" PRIu64 " to %" PRId64 " ms", maxJitter, jitter); + maxJitter = (uint64_t)jitter; + } + if (curJitter < (uint64_t)jitter){curJitter = (uint64_t)jitter;} + } + return maxJitter; + } + }; + + /// Updates the metadata given the packet's properties. + void Meta::update(uint64_t packTime, int64_t packOffset, uint32_t packTrack, uint64_t packDataSize, + uint64_t packBytePos, bool isKeyframe, uint64_t packSendSize){ + ///\todo warning Re-Implement Ivec + if (getLive()){ + static std::map theJitters; + setMinKeepAway(packTrack, theJitters[packTrack].addPack(packTime)); + } + + DONTEVEN_MSG("Updating meta with: t=%" PRIu64 ", o=%" PRId64 ", s=%" PRIu64 ", t=%" PRIu32 + ", p=%" PRIu64, + packTime, packOffset, packDataSize, packTrack, packBytePos); + if (!packSendSize){ + // time and trackID are part of the 20-byte header. + // the container object adds 4 bytes (plus 2+namelen for each content, see below) + // offset, if non-zero, adds 9 bytes (integer type) and 8 bytes (2+namelen) + // bpos, if >= 0, adds 9 bytes (integer type) and 6 bytes (2+namelen) + // keyframe, if true, adds 9 bytes (integer type) and 10 bytes (2+namelen) + // data adds packDataSize+5 bytes (string type) and 6 bytes (2+namelen) + packSendSize = 24 + (packOffset ? 17 : 0) + (packBytePos > 0 ? 15 : 0) + + (isKeyframe ? 19 : 0) + packDataSize + 11; + } + + if ((packBytePos > 0) != (stream.getInt(streamVodField) == 1)){ + INFO_MSG("Changing stream from %s to %s (bPos=%" PRIu64 ")", + stream.getInt(streamVodField) ? "VoD" : "live", (packBytePos >= 0) ? "Vod" : "live", packBytePos); + stream.setInt(streamVodField, packBytePos > 0 ? 1 : 0); + stream.setInt(streamLiveField, packBytePos > 0 ? 0 : 1); + } + + size_t tNumber = packTrack; + std::map::iterator it = tracks.find(tNumber); + if (it == tracks.end()){ + ERROR_MSG("Could not buffer packet for track %zu: track not found", tNumber); + return; + } + + Track &t = it->second; + if (packTime < getLastms(tNumber)){ + static bool warned = false; + if (!warned){ + ERROR_MSG("Received packets for track %zu in wrong order (%" PRIu64 " < %" PRIu64 + ") - ignoring! Further messages on HIGH level.", + tNumber, packTime, getLastms(tNumber)); + warned = true; + }else{ + HIGH_MSG("Received packets for track %zu in wrong order (%" PRIu64 " < %" PRIu64 + ") - ignoring!", + tNumber, packTime, getLastms(tNumber)); + } + return; + } + + uint32_t newPartNum = t.parts.getEndPos(); + if ((newPartNum - t.parts.getDeleted()) >= t.parts.getRCount()){ + resizeTrack(tNumber, t.fragments.getRCount(), t.keys.getRCount(), t.parts.getRCount() * 2, + t.keys.getRCount()); + } + t.parts.addRecords(1); + t.parts.setInt(t.partSizeField, packDataSize, newPartNum); + t.parts.setInt(t.partOffsetField, packOffset, newPartNum); + if (newPartNum){ + t.parts.setInt(t.partDurationField, packTime - getLastms(tNumber), newPartNum - 1); + t.parts.setInt(t.partDurationField, packTime - getLastms(tNumber), newPartNum); + }else{ + t.parts.setInt(t.partDurationField, 0, newPartNum); + } + t.track.setInt(t.trackLastmsField, packTime); + + uint32_t newKeyNum = t.keys.getEndPos(); + if (isKeyframe || newKeyNum == 0 || + (getType(tNumber) != "video" && packTime >= AUDIO_KEY_INTERVAL && + packTime - t.keys.getInt(t.keyTimeField, newKeyNum - 1) >= AUDIO_KEY_INTERVAL)){ + if ((newKeyNum - t.keys.getDeleted()) >= t.keys.getRCount()){ + resizeTrack(tNumber, t.fragments.getRCount(), t.keys.getRCount() * 2, t.parts.getRCount(), + t.keys.getRCount() * 2); + } + t.keys.addRecords(1); + t.keys.setInt(t.keyBposField, packBytePos, newKeyNum); + t.keys.setInt(t.keyTimeField, packTime, newKeyNum); + t.keys.setInt(t.keyPartsField, 0, newKeyNum); + t.keys.setInt(t.keyDurationField, 0, newKeyNum); + t.keys.setInt(t.keySizeField, 0, newKeyNum); + t.keys.setInt(t.keyNumberField, newKeyNum, newKeyNum); + if (newKeyNum){ + t.keys.setInt(t.keyFirstPartField, + t.keys.getInt(t.keyFirstPartField, newKeyNum - 1) + + t.keys.getInt(t.keyPartsField, newKeyNum - 1), + newKeyNum); + // Update duration of previous key too + t.keys.setInt(t.keyDurationField, packTime - t.keys.getInt(t.keyTimeField, newKeyNum - 1), + newKeyNum - 1); + }else{ + t.keys.setInt(t.keyFirstPartField, 0, newKeyNum); + } + if (packBytePos){t.track.setInt(t.trackFirstmsField, t.keys.getInt(t.keyTimeField, 0));} + + uint32_t newFragNum = t.fragments.getEndPos(); + if (newFragNum == 0 || + (packTime > getMinimumFragmentDuration() && + (packTime - getMinimumFragmentDuration()) >= + t.keys.getInt(t.keyTimeField, t.fragments.getInt(t.fragmentFirstKeyField, newFragNum - 1)))){ + if ((newFragNum - t.fragments.getDeleted()) >= t.fragments.getRCount()){ + resizeTrack(tNumber, t.fragments.getRCount() * 2, t.keys.getRCount(), t.parts.getRCount(), + t.keys.getRCount()); + } + t.fragments.addRecords(1); + if (newFragNum){ + t.fragments.setInt(t.fragmentDurationField, + packTime - t.keys.getInt(t.keyTimeField, t.fragments.getInt(t.fragmentFirstKeyField, + newFragNum - 1)), + newFragNum - 1); + + uint64_t totalBytes = 0; + uint64_t totalDuration = 0; + + for (size_t fragIdx = t.fragments.getStartPos(); fragIdx < newFragNum; fragIdx++){ + totalBytes += t.fragments.getInt(t.fragmentSizeField, fragIdx); + totalDuration += t.fragments.getInt(t.fragmentDurationField, fragIdx); } + setBps(tNumber, (totalDuration ? (totalBytes * 1000) / totalDuration : 0)); + + setMaxBps(tNumber, std::max(getMaxBps(tNumber), + (t.fragments.getInt(t.fragmentSizeField, newFragNum - 1) * 1000) / + t.fragments.getInt(t.fragmentDurationField, newFragNum - 1))); + } + t.fragments.setInt(t.fragmentFirstKeyField, newKeyNum, newFragNum); + t.fragments.setInt(t.fragmentDurationField, 0, newFragNum); + t.fragments.setInt(t.fragmentSizeField, 0, newFragNum); + t.fragments.setInt(t.fragmentKeysField, 1, newFragNum); + t.fragments.setInt(t.fragmentFirstKeyField, t.keys.getInt(t.keyNumberField, newKeyNum), newFragNum); + }else{ + t.fragments.setInt(t.fragmentKeysField, + t.fragments.getInt(t.fragmentKeysField, newFragNum - 1) + 1, newFragNum - 1); + } + }else{ + uint32_t lastKeyNum = t.keys.getEndPos() - 1; + t.keys.setInt(t.keyDurationField, + t.keys.getInt(t.keyDurationField, lastKeyNum) + + t.parts.getInt(t.partDurationField, newPartNum - 1), + lastKeyNum); + } + + uint32_t lastKeyNum = t.keys.getEndPos() - 1; + t.keys.setInt(t.keyPartsField, t.keys.getInt(t.keyPartsField, lastKeyNum) + 1, lastKeyNum); + t.keys.setInt(t.keySizeField, t.keys.getInt(t.keySizeField, lastKeyNum) + packSendSize, lastKeyNum); + uint32_t lastFragNum = t.fragments.getEndPos() - 1; + t.fragments.setInt(t.fragmentSizeField, + t.fragments.getInt(t.fragmentSizeField, lastFragNum) + packDataSize, lastFragNum); + markUpdated(tNumber); + } + + /// Prints the metadata and tracks in human-readable format + std::string Meta::toPrettyString() const{ + std::stringstream r; + r << "Metadata for stream " << streamName << std::endl; + r << stream.toPrettyString(); + for (std::map::const_iterator it = tracks.begin(); it != tracks.end(); it++){ + r << " Track " << it->first << ": " << it->second.track.toPrettyString() << std::endl; + } + return r.str(); + } + + /// Loops over the active tracks, returning the index of the track with the given ID for the given + /// process. + size_t Meta::trackIDToIndex(size_t trackID, size_t pid) const{ + for (size_t i = 0; i < trackList.getPresent(); i++){ + if (pid && trackList.getInt(trackPidField, i) != pid){continue;} + if (trackList.getInt(trackIdField, i) == trackID){return i;} + } + return INVALID_TRACK_ID; + } + + /// Returns a pretty-printed (optionally unique) name for the given track + std::string Meta::getTrackIdentifier(size_t idx, bool unique) const{ + std::stringstream result; + std::string type = getType(idx); + if (type == ""){ + result << "metadata_" << idx; + return result.str(); + } + result << type << "_"; + result << getCodec(idx) << "_"; + if (type == "audio"){ + result << getChannels(idx) << "ch_"; + result << getRate(idx) << "hz"; + }else if (type == "video"){ + result << getWidth(idx) << "x" << getHeight(idx) << "_"; + result << (double)getFpks(idx) / 1000 << "fps"; + } + if (getLang(idx) != "" && getLang(idx) != "und"){result << "_" << getLang(idx);} + if (unique){result << "_" << idx;} + return result.str(); + } + + const Util::RelAccX &Meta::parts(size_t idx) const{return tracks.at(idx).parts;} + Util::RelAccX &Meta::keys(size_t idx){return tracks.at(idx).keys;} + const Util::RelAccX &Meta::keys(size_t idx) const{return tracks.at(idx).keys;} + const Util::RelAccX &Meta::fragments(size_t idx) const{return tracks.at(idx).fragments;} + const Util::RelAccX &Meta::pages(size_t idx) const{return tracks.at(idx).pages;} + Util::RelAccX &Meta::pages(size_t idx){return tracks.at(idx).pages;} + + /// Wipes internal structures, also marking as outdated and deleting memory structures if in + /// master mode. + void Meta::clear(){ + if (isMemBuf){ + isMemBuf = false; + free(streamMemBuf); + streamMemBuf = 0; + for (std::map::iterator it = tMemBuf.begin(); it != tMemBuf.end(); it++){ + free(it->second); + } + tMemBuf.clear(); + sizeMemBuf.clear(); + } + if (isMaster){ + std::set toRemove; + for (std::map::iterator it = tM.begin(); it != tM.end(); it++){ + if (!it->second.mapped){continue;} + toRemove.insert(it->first); + } + for (std::set::iterator it = toRemove.begin(); it != toRemove.end(); it++){ + removeTrack(*it); + } + if (streamPage.mapped && stream.isReady()){stream.setExit();} + streamPage.master = true; + } + streamPage.close(); + tM.clear(); + tracks.clear(); + isMaster = true; + streamName = ""; + } + + /// Makes a minimally-sized copy of the given Meta object. + /// Only used internally by the remap() function. + void Meta::minimalFrom(const DTSC::Meta &src){ + clear(); + sBufMem(); + streamInit(); + stream.flowFrom(src.stream); + + for (int i = 0; i < src.trackList.getPresent(); i++){ + Track &t = tracks[i]; + tMemBuf[i] = (char *)malloc(SHM_STREAM_TRACK_LEN); + sizeMemBuf[i] = SHM_STREAM_TRACK_LEN; + memset(tMemBuf[i], 0, SHM_STREAM_TRACK_LEN); + t.track = Util::RelAccX(tMemBuf[i], false); + initializeTrack(t); + + t.track.flowFrom(src.tracks.at(i).track); + } + } + + /// Re-maps the current Meta object by making a minimal copy in a temporary object, then flowing + /// the current object from the temporary object. Not currently used by anything...? + void Meta::remap(const std::string &_streamName){ + Meta M; + M.minimalFrom(*this); + + reInit(_streamName.size() ? _streamName : streamName); + + stream.flowFrom(M.stream); + for (size_t i = 0; i < M.trackList.getPresent(); i++){ + Track &t = tracks[i]; + + char pageName[NAME_BUFFER_SIZE]; + snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_TM, streamName.c_str(), getpid(), i); + + tM[i].init(pageName, SHM_STREAM_TRACK_LEN, true); + tM[i].master = false; + + t.track = Util::RelAccX(tM[i].mapped, false); + initializeTrack(t); + t.track.flowFrom(M.tracks[i].track); + } + } + + ///\brief Determines the "packed" size of a Meta object + uint64_t Meta::getSendLen(bool skipDynamic, std::set selectedTracks) const{ + uint64_t dataLen = 48; // + (merged ? 17 : 0) + (bufferWindow ? 24 : 0) + 21; + for (std::map::const_iterator it = tracks.begin(); it != tracks.end(); it++){ + if (!it->second.parts.getPresent()){continue;} + if (!selectedTracks.size() || selectedTracks.count(it->first)){ + dataLen += (124 + getInit(it->first).size() + getCodec(it->first).size() + + getType(it->first).size() + getTrackIdentifier(it->first, true).size()); + if (!skipDynamic){ + dataLen += ((it->second.fragments.getPresent() * DTSH_FRAGMENT_SIZE) + 16); + dataLen += ((it->second.keys.getPresent() * DTSH_KEY_SIZE) + 11); + dataLen += ((it->second.keys.getPresent() * 4) + 15); + dataLen += ((it->second.parts.getPresent() * DTSH_PART_SIZE) + 12); + // dataLen += ivecs.size() * 8 + 12; /*LTS*/ + if (it->second.track.getInt("missedFrags")){dataLen += 23;} + } + std::string lang = getLang(it->first); + if (lang.size() && lang != "und"){dataLen += 11 + lang.size();} + if (getType(it->first) == "audio"){ + dataLen += 49; + }else if (getType(it->first) == "video"){ + dataLen += 48; } } } - if (insert){ - if (tmpPos.seekTime > 0xffffffffffffff00ll){tmpPos.seekTime = 0;} - currentPositions.insert(tmpPos); - }else{ - seek_time(myPack.getTime(), myPack.getTrackId(), true); - } - seek_bpos(tempLoc); - }else{ - seek_time(thisPos.seekTime, thisPos.trackID); - fseek(F, thisPos.bytePos, SEEK_SET); - } -} - -void DTSC::File::parseNext(){ - char header_buffer[4] ={0, 0, 0, 0}; - lastreadpos = ftell(F); - if (fread(header_buffer, 4, 1, F) != 1){ - if (feof(F)){ - DEBUG_MSG(DLVL_DEVEL, "End of file reached @ %d", (int)lastreadpos); - }else{ - DEBUG_MSG(DLVL_ERROR, "Could not read header @ %d", (int)lastreadpos); - } - myPack.null(); - return; - } - long long unsigned int version = 0; - if (memcmp(header_buffer, DTSC::Magic_Packet, 4) == 0 || memcmp(header_buffer, DTSC::Magic_Command, 4) == 0 || - memcmp(header_buffer, DTSC::Magic_Header, 4) == 0){ - version = 1; - } - if (memcmp(header_buffer, DTSC::Magic_Packet2, 4) == 0){version = 2;} - if (version == 0){ - DEBUG_MSG(DLVL_ERROR, "Invalid packet header @ %#x: %.4s", (unsigned int)lastreadpos, (char *)buffer); - myPack.null(); - return; - } - if (fread(buffer, 4, 1, F) != 1){ - DEBUG_MSG(DLVL_ERROR, "Could not read packet size @ %#x", (unsigned int)lastreadpos); - myPack.null(); - return; - } - long packSize = ntohl(((unsigned long *)buffer)[0]); - char *packBuffer = (char *)malloc(packSize + 8); - memcpy(packBuffer, header_buffer, 4); - memcpy(packBuffer + 4, buffer, 4); - if (fread((void *)(packBuffer + 8), packSize, 1, F) != 1){ - DEBUG_MSG(DLVL_ERROR, "Could not read packet @ %d", (int)lastreadpos); - myPack.null(); - free(packBuffer); - return; - } - myPack.reInit(packBuffer, packSize + 8); - free(packBuffer); -} - -/// Returns the byte positon of the start of the last packet that was read. -long long int DTSC::File::getLastReadPos(){ - return lastreadpos; -} - -/// Returns the internal buffer of the last read packet in raw binary format. -DTSC::Packet &DTSC::File::getPacket(){ - return myPack; -} - -bool DTSC::File::seek_time(unsigned int ms, unsigned int trackNo, bool forceSeek){ - seekPos tmpPos; - tmpPos.trackID = trackNo; - if (!forceSeek && myPack && ms >= myPack.getTime() && trackNo >= myPack.getTrackId()){ - tmpPos.seekTime = myPack.getTime(); - tmpPos.bytePos = getBytePos(); /* - if (trackNo == myPack.getTrackId()){ - tmpPos.bytePos += myPack.getDataLen(); + if (sourceURI.size()){ + dataLen += 13 + sourceURI.size(); } */ - }else{ - tmpPos.seekTime = 0; - tmpPos.bytePos = 0; + return dataLen + 8; // add 8 bytes header } - if (reachedEOF()){ - clearerr(F); - seek_bpos(0); - tmpPos.bytePos = 0; - tmpPos.seekTime = 0; + + ///\brief Converts a short to a char* + inline char *c16(short input){ + static char result[2]; + Bit::htobs(result, input); + return result; } - DTSC::Track &trackRef = metadata.tracks[trackNo]; - for (unsigned int i = 0; i < trackRef.keys.size(); i++){ - long keyTime = trackRef.keys[i].getTime(); - if (keyTime > ms){break;} - if ((long long unsigned int)keyTime > tmpPos.seekTime){ - tmpPos.seekTime = keyTime; - tmpPos.bytePos = trackRef.keys[i].getBpos(); - } + + ///\brief Converts a short to a char* + inline char *c24(int input){ + static char result[3]; + Bit::htob24(result, input); + return result; } - bool foundPacket = false; - while (!foundPacket){ - lastreadpos = ftell(F); - if (reachedEOF()){ - DEBUG_MSG(DLVL_WARN, "Reached EOF during seek to %u in track %d - aborting @ %lld", ms, trackNo, lastreadpos); - return false; + + ///\brief Converts an integer to a char* + inline char *c32(int input){ + static char result[4]; + Bit::htobl(result, input); + return result; + } + + ///\brief Converts a long long to a char* + inline char *c64(long long int input){ + static char result[8]; + Bit::htobll(result, input); + return result; + } + + /// Writes the current Meta object in DTSH format to the given filename + void Meta::toFile(const std::string &fName) const{ + std::string lVars; + size_t lVarSize = 0; + if (inputLocalVars.size()){ + lVars = inputLocalVars.toString(); + lVarSize = 2 + 14 + 5 + lVars.size(); } - // Seek to first packet after ms. - seek_bpos(tmpPos.bytePos); - // read the header - char header[20]; - if (fread((void *)header, 20, 1, F) != 1){ - DEBUG_MSG(DLVL_WARN, "Could not read header from file. Much sadface."); - return false; + + std::ofstream oFile(fName.c_str(), std::ios::binary | std::ios::ate); + oFile.write(DTSC::Magic_Header, 4); + oFile.write(c32(lVarSize + getSendLen() - 8), 4); + oFile.write("\340", 1); + oFile.write("\000\003vod\001\000\000\000\000\000\000\000\001", 14); + oFile.write("\000\007version\001", 10); + oFile.write(c64(DTSH_VERSION), 8); + if (lVarSize){ + oFile.write("\000\016inputLocalVars\002", 17); + oFile.write(c32(lVars.size()), 4); + oFile.write(lVars.data(), lVars.size()); } - // check if packetID matches, if not, skip size + 8 bytes. - int packSize = ntohl(((int *)header)[1]); - unsigned int packID = ntohl(((int *)header)[2]); - if (memcmp(header, Magic_Packet2, 4) != 0 || packID != trackNo){ - if (memcmp(header, "DT", 2) != 0){ - DEBUG_MSG(DLVL_WARN, "Invalid header during seek to %u in track %d @ %lld - resetting bytePos from %lld to zero", - ms, trackNo, lastreadpos, tmpPos.bytePos); - tmpPos.bytePos = 0; - continue; + oFile.write("\000\006tracks\340", 9); + for (std::map::const_iterator it = tracks.begin(); it != tracks.end(); it++){ + if (!it->second.parts.getPresent()){continue;} + std::string tmp = getTrackIdentifier(it->first, true); + oFile.write(c16(tmp.size()), 2); + oFile.write(tmp.data(), tmp.size()); + oFile.write("\340", 1); // Begin track object + + size_t fragCount = it->second.fragments.getPresent(); + oFile.write("\000\011fragments\002", 12); + oFile.write(c32(fragCount * DTSH_FRAGMENT_SIZE), 4); + for (size_t i = 0; i < fragCount; i++){ + oFile.write(c32(it->second.fragments.getInt("duration", i)), 4); + oFile.put(it->second.fragments.getInt("keys", i)); + oFile.write(c32(it->second.fragments.getInt("firstkey", i) + 1), 4); + oFile.write(c32(it->second.fragments.getInt("size", i)), 4); } - tmpPos.bytePos += 8 + packSize; - continue; + + size_t keyCount = it->second.keys.getPresent(); + oFile.write("\000\004keys\002", 7); + oFile.write(c32(keyCount * DTSH_KEY_SIZE), 4); + for (size_t i = 0; i < keyCount; i++){ + oFile.write(c64(it->second.keys.getInt("bpos", i)), 8); + oFile.write(c24(it->second.keys.getInt("duration", i)), 3); + oFile.write(c32(it->second.keys.getInt("number", i) + 1), 4); + oFile.write(c16(it->second.keys.getInt("parts", i)), 2); + oFile.write(c64(it->second.keys.getInt("time", i)), 8); + } + oFile.write("\000\010keysizes\002,", 11); + oFile.write(c32(keyCount * 4), 4); + for (size_t i = 0; i < keyCount; i++){ + oFile.write(c32(it->second.keys.getInt("size", i)), 4); + } + + size_t partCount = it->second.parts.getPresent(); + oFile.write("\000\005parts\002", 8); + oFile.write(c32(partCount * DTSH_PART_SIZE), 4); + for (size_t i = 0; i < partCount; i++){ + oFile.write(c24(it->second.parts.getInt("size", i)), 3); + oFile.write(c24(it->second.parts.getInt("duration", i)), 3); + oFile.write(c24(it->second.parts.getInt("offset", i)), 3); + } + + oFile.write("\000\007trackid\001", 10); + oFile.write(c64(it->second.track.getInt("id")), 8); + + if (it->second.track.getInt("missedFrags")){ + oFile.write("\000\014missed_frags\001", 15); + oFile.write(c64(it->second.track.getInt("missedFrags")), 8); + } + + oFile.write("\000\007firstms\001", 10); + oFile.write(c64(it->second.track.getInt("firstms")), 8); + oFile.write("\000\006lastms\001", 9); + oFile.write(c64(it->second.track.getInt("lastms")), 8); + + oFile.write("\000\003bps\001", 6); + oFile.write(c64(it->second.track.getInt("bps")), 8); + + oFile.write("\000\006maxbps\001", 9); + oFile.write(c64(it->second.track.getInt("maxbps")), 8); + + tmp = getInit(it->first); + oFile.write("\000\004init\002", 7); + oFile.write(c32(tmp.size()), 4); + oFile.write(tmp.data(), tmp.size()); + + tmp = getCodec(it->first); + oFile.write("\000\005codec\002", 8); + oFile.write(c32(tmp.size()), 4); + oFile.write(tmp.data(), tmp.size()); + + tmp = getLang(it->first); + if (tmp.size() && tmp != "und"){ + oFile.write("\000\004lang\002", 7); + oFile.write(c32(tmp.size()), 4); + oFile.write(tmp.data(), tmp.size()); + } + + tmp = getType(it->first); + oFile.write("\000\004type\002", 7); + oFile.write(c32(tmp.size()), 4); + oFile.write(tmp.data(), tmp.size()); + + if (tmp == "audio"){ + oFile.write("\000\004rate\001", 7); + oFile.write(c64(it->second.track.getInt("rate")), 8); + oFile.write("\000\004size\001", 7); + oFile.write(c64(it->second.track.getInt("size")), 8); + oFile.write("\000\010channels\001", 11); + oFile.write(c64(it->second.track.getInt("channels")), 8); + }else if (tmp == "video"){ + oFile.write("\000\005width\001", 8); + oFile.write(c64(it->second.track.getInt("width")), 8); + oFile.write("\000\006height\001", 9); + oFile.write(c64(it->second.track.getInt("height")), 8); + oFile.write("\000\004fpks\001", 7); + oFile.write(c64(it->second.track.getInt("fpks")), 8); + } + oFile.write("\000\000\356", 3); // End this track Object } - // get timestamp of packet, if too large, break, if not, skip size bytes. - long long unsigned int myTime = ((long long unsigned int)ntohl(((int *)header)[3]) << 32); - myTime += ntohl(((int *)header)[4]); - tmpPos.seekTime = myTime; - if (myTime >= ms){ - foundPacket = true; + oFile.write("\000\000\356", 3); // End tracks object + oFile.write("\000\000\356", 3); // End global object + oFile.close(); + } + + /// Converts the current Meta object to JSON format + void Meta::toJSON(JSON::Value &res, bool skipDynamic, bool tracksOnly) const{ + res.null(); + if (getLive()){ + res["live"] = 1u; }else{ - tmpPos.bytePos += 8 + packSize; - continue; + res["vod"] = 1u; + } + res["version"] = DTSH_VERSION; + if (getBufferWindow()){res["buffer_window"] = getBufferWindow();} + if (getSource() != ""){res["source"] = getSource();} + + if (!skipDynamic){ + WARN_MSG("Skipping dynamic stuff even though skipDynamic is set to false"); + } + + std::set validTracks = getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + JSON::Value &trackJSON = res["tracks"][getTrackIdentifier(*it, true)]; + std::string type = getType(*it); + + trackJSON["codec"] = getCodec(*it); + trackJSON["type"] = type; + trackJSON["idx"] = *it; + trackJSON["trackid"] = getID(*it); + trackJSON["init"] = getInit(*it); + trackJSON["firstms"] = getFirstms(*it); + trackJSON["lastms"] = getLastms(*it); + trackJSON["bps"] = getBps(*it); + trackJSON["maxbps"] = getMaxBps(*it); + if (!skipDynamic && getLive()){ + if (getMissedFragments(*it)){trackJSON["missed_frags"] = getMissedFragments(*it);} + if (getMinKeepAway(*it)){trackJSON["keepaway"] = getMinKeepAway(*it);} + } + + if (getLang(*it) != "" && getLang(*it) != "und"){trackJSON["lang"] = getLang(*it);} + if (type == "audio"){ + trackJSON["rate"] = getRate(*it); + trackJSON["size"] = getSize(*it); + trackJSON["channels"] = getChannels(*it); + }else if (type == "video"){ + trackJSON["width"] = getWidth(*it); + trackJSON["height"] = getHeight(*it); + trackJSON["fpks"] = getFpks(*it); + } + } + if (tracksOnly){ + JSON::Value v = res["tracks"]; + res = v; } } - // DEBUG_MSG(DLVL_HIGH, "Seek to %u:%d resulted in %lli", trackNo, ms, tmpPos.seekTime); - if (tmpPos.seekTime > 0xffffffffffffff00ll){tmpPos.seekTime = 0;} - currentPositions.insert(tmpPos); - return true; -} -/// Attempts to seek to the given time in ms within the file. -/// Returns true if successful, false otherwise. -bool DTSC::File::seek_time(unsigned int ms){ - currentPositions.clear(); - if (selectedTracks.size()){ - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - seek_time(ms, (*it), true); + /// Sends the current Meta object through a socket in DTSH format + void Meta::send(Socket::Connection &conn, bool skipDynamic, std::set selectedTracks, bool reID) const{ + conn.SendNow(DTSC::Magic_Header, 4); + conn.SendNow(c32(getSendLen(skipDynamic, selectedTracks) - 8), 4); + conn.SendNow("\340", 1); + conn.SendNow("\000\003vod\001\000\000\000\000\000\000\000\001", 14); + conn.SendNow("\000\007version\001", 10); + conn.SendNow(c64(DTSH_VERSION), 8); + conn.SendNow("\000\006tracks\340", 9); + for (std::set::const_iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + std::string tmp = getTrackIdentifier(*it, true); + conn.SendNow(c16(tmp.size()), 2); + conn.SendNow(tmp.data(), tmp.size()); + conn.SendNow("\340", 1); // Begin track object + + if (!skipDynamic){ + const Util::RelAccX &fragments = tracks.at(*it).fragments; + const Util::RelAccX &keys = tracks.at(*it).keys; + const Util::RelAccX &parts = tracks.at(*it).parts; + + size_t fragBegin = fragments.getStartPos(); + size_t fragCount = fragments.getPresent(); + size_t keyBegin = keys.getStartPos(); + size_t keyCount = keys.getPresent(); + size_t partBegin = parts.getStartPos(); + size_t partCount = parts.getPresent(); + + conn.SendNow("\000\011fragments\002", 12); + conn.SendNow(c32(fragCount * DTSH_FRAGMENT_SIZE), 4); + for (size_t i = 0; i < fragCount; i++){ + conn.SendNow(c32(fragments.getInt("duration", i + fragBegin)), 4); + conn.SendNow(std::string(1, (char)fragments.getInt("keys", i + fragBegin))); + + conn.SendNow(c32(fragments.getInt("firstkey", i + fragBegin)), 4); + conn.SendNow(c32(fragments.getInt("size", i + fragBegin)), 4); + } + + conn.SendNow("\000\004keys\002", 7); + conn.SendNow(c32(keyCount * DTSH_KEY_SIZE), 4); + for (size_t i = 0; i < keyCount; i++){ + conn.SendNow(c64(keys.getInt("bpos", i + fragBegin)), 8); + conn.SendNow(c24(keys.getInt("duration", i + keyBegin)), 3); + conn.SendNow(c32(keys.getInt("number", i + keyBegin)), 4); + conn.SendNow(c16(keys.getInt("parts", i + keyBegin)), 2); + conn.SendNow(c64(keys.getInt("time", i + keyBegin)), 8); + } + conn.SendNow("\000\010keysizes\002,", 11); + conn.SendNow(c32(keyCount * 4), 4); + for (size_t i = 0; i < keyCount; i++){ + conn.SendNow(c32(keys.getInt("size", i + keyBegin)), 4); + } + + conn.SendNow("\000\005parts\002", 8); + conn.SendNow(c32(partCount * DTSH_PART_SIZE), 4); + for (size_t i = 0; i < partCount; i++){ + conn.SendNow(c24(parts.getInt("size", i + partBegin)), 3); + conn.SendNow(c24(parts.getInt("duration", i + partBegin)), 3); + conn.SendNow(c24(parts.getInt("offset", i + partBegin)), 3); + } + } + + const Util::RelAccX &track = tracks.at(*it).track; + conn.SendNow("\000\007trackid\001", 10); + if (reID){ + conn.SendNow(c64((*it) + 1), 8); + }else{ + conn.SendNow(c64(track.getInt("id")), 8); + } + + if (!skipDynamic && track.getInt("missedFrags")){ + conn.SendNow("\000\014missed_frags\001", 15); + conn.SendNow(c64(track.getInt("missedFrags")), 8); + } + + conn.SendNow("\000\007firstms\001", 10); + conn.SendNow(c64(track.getInt("firstms")), 8); + conn.SendNow("\000\006lastms\001", 9); + conn.SendNow(c64(track.getInt("lastms")), 8); + + conn.SendNow("\000\003bps\001", 6); + conn.SendNow(c64(track.getInt("bps")), 8); + + conn.SendNow("\000\006maxbps\001", 9); + conn.SendNow(c64(track.getInt("maxbps")), 8); + + tmp = getInit(*it); + conn.SendNow("\000\004init\002", 7); + conn.SendNow(c32(tmp.size()), 4); + conn.SendNow(tmp.data(), tmp.size()); + + tmp = getCodec(*it); + conn.SendNow("\000\005codec\002", 8); + conn.SendNow(c32(tmp.size()), 4); + conn.SendNow(tmp.data(), tmp.size()); + + tmp = getLang(*it); + if (tmp.size() && tmp != "und"){ + conn.SendNow("\000\004lang\002", 7); + conn.SendNow(c32(tmp.size()), 4); + conn.SendNow(tmp.data(), tmp.size()); + } + + tmp = getType(*it); + conn.SendNow("\000\004type\002", 7); + conn.SendNow(c32(tmp.size()), 4); + conn.SendNow(tmp.data(), tmp.size()); + + if (tmp == "audio"){ + conn.SendNow("\000\004rate\001", 7); + conn.SendNow(c64(track.getInt("rate")), 8); + conn.SendNow("\000\004size\001", 7); + conn.SendNow(c64(track.getInt("size")), 8); + conn.SendNow("\000\010channels\001", 11); + conn.SendNow(c64(track.getInt("channels")), 8); + }else if (tmp == "video"){ + conn.SendNow("\000\005width\001", 8); + conn.SendNow(c64(track.getInt("width")), 8); + conn.SendNow("\000\006height\001", 9); + conn.SendNow(c64(track.getInt("height")), 8); + conn.SendNow("\000\004fpks\001", 7); + conn.SendNow(c64(track.getInt("fpks")), 8); + } + conn.SendNow("\000\000\356", 3); // End this track Object } + conn.SendNow("\000\000\356", 3); // End tracks object + conn.SendNow("\000\000\356", 3); // End global object } - return true; -} -bool DTSC::File::seek_bpos(int bpos){ - if (fseek(F, bpos, SEEK_SET) == 0){return true;} - return false; -} - -void DTSC::File::rewritePacket(std::string &newPacket, int bytePos){ - fseek(F, bytePos, SEEK_SET); - fwrite(newPacket.c_str(), newPacket.size(), 1, F); - fseek(F, 0, SEEK_END); - if (ftell(F) > endPos){endPos = ftell(F);} -} - -void DTSC::File::writePacket(std::string &newPacket){ - fseek(F, 0, SEEK_END); - fwrite(newPacket.c_str(), newPacket.size(), 1, F); // write contents - fseek(F, 0, SEEK_END); - endPos = ftell(F); -} - -void DTSC::File::writePacket(JSON::Value &newPacket){ - writePacket(newPacket.toNetPacked()); -} - -bool DTSC::File::atKeyframe(){ - if (myPack.getFlag("keyframe")){return true;} - long long int bTime = myPack.getTime(); - DTSC::Track &trackRef = metadata.tracks[myPack.getTrackId()]; - for (unsigned int i = 0; i < trackRef.keys.size(); i++){ - if (trackRef.keys[i].getTime() >= bTime){return (trackRef.keys[i].getTime() == bTime);} + /// Returns true if the given track index is marked as valid and present in the tracks structure. + bool Meta::trackLoaded(size_t idx) const{ + if (!trackValid(idx)){return false;} + if (!tracks.count(idx)){ + INFO_MSG("Track %zu is not yet loaded", idx); + return false; + } + return true; } - return false; -} -void DTSC::File::selectTracks(std::set &tracks){ - selectedTracks = tracks; - currentPositions.clear(); - seek_time(0); -} - -/// Close the file if open -DTSC::File::~File(){ - if (F){ - fclose(F); - F = 0; + /// Returns true if the given track index is marked as valid. For this the track does not have to + /// be loaded as well + bool Meta::trackValid(size_t idx) const{ + if (idx > trackList.getPresent()){return false;} + return trackList.getInt(trackValidField, idx); } - free(buffer); -} + + /// Returns the current highest track index (zero-based). + size_t Meta::trackCount() const{return trackList.getPresent();} + + /// Returns the index the first video track, or the first track. + /// Will print a WARN-level message if there are no tracks. + size_t Meta::mainTrack() const{ + if (!trackList.getPresent()){return INVALID_TRACK_ID;} + std::set validTracks = getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (getCodec(*it) == "video"){return *it;} + } + return *validTracks.begin(); + } + + /// Returns the duration of the longest fragment in the given track. + uint32_t Meta::biggestFragment(uint32_t idx) const{ + if (!trackList.getPresent()){return 0;} + uint32_t trackIdx = (idx == INVALID_TRACK_ID ? mainTrack() : idx); + if (!tM.count(trackIdx)){return 0;} + DTSC::Fragments fragments(tracks.at(trackIdx).fragments); + uint32_t firstFragment = fragments.getFirstValid(); + uint32_t endFragment = fragments.getEndValid(); + uint32_t ret = 0; + for (uint32_t i = firstFragment; i < endFragment; i++){ + uint32_t fragDur = fragments.getDuration(i); + if (fragDur > ret){ret = fragDur;} + } + return ret; + } + + bool Meta::tracksAlign(size_t idx1, size_t idx2) const{ + if (!tM.count(idx1) || !tM.count(idx2)){return false;} + DTSC::Fragments frag1(tracks.at(idx1).fragments); + DTSC::Fragments frag2(tracks.at(idx2).fragments); + if (frag1.getFirstValid() >= frag2.getFirstValid()){ + size_t firstValid = frag1.getFirstValid(); + size_t firstTime = getTimeForFragmentIndex(idx1, firstValid); + size_t secondIndex = getFragmentIndexForTime(idx2, firstTime); + size_t count = std::min(frag1.getValidCount(), frag2.getEndValid() - secondIndex); + if (count <= 2){ + INFO_MSG("Determining track alignment between track %zu and %zu based on %zu fragments, " + "might be inaccurate", + idx1, idx2, count); + } + for (size_t i = 0; i < count; i++){ + if (getTimeForFragmentIndex(idx1, firstValid + i) != getTimeForFragmentIndex(idx2, secondIndex + i)){ + return false; + } + } + }else{ + size_t firstValid = frag2.getFirstValid(); + size_t firstTime = getTimeForFragmentIndex(idx2, firstValid); + size_t secondIndex = getFragmentIndexForTime(idx1, firstTime); + size_t count = std::min(frag2.getValidCount(), frag1.getEndValid() - secondIndex); + if (count <= 2){ + INFO_MSG("Determining track alignment between track %zu and %zu based on %zu fragments, " + "might be inaccurate", + idx1, idx2, count); + } + for (size_t i = 0; i < count; i++){ + if (getTimeForFragmentIndex(idx2, firstValid + i) != getTimeForFragmentIndex(idx1, secondIndex + i)){ + return false; + } + } + } + return true; + } + + /// Gets indice of the fragment containing timestamp, or last fragment if nowhere. + uint32_t Meta::getFragmentIndexForTime(uint32_t idx, uint64_t timestamp) const{ + DTSC::Fragments fragments(tracks.at(idx).fragments); + DTSC::Keys keys(tracks.at(idx).keys); + uint32_t firstFragment = fragments.getFirstValid(); + uint32_t endFragment = fragments.getEndValid(); + for (size_t i = firstFragment; i < endFragment; i++){ + uint32_t keyNumber = fragments.getFirstKey(i); + uint32_t duration = fragments.getDuration(i); + if (timestamp < keys.getTime(keyNumber) + duration){return i;} + } + if (endFragment > firstFragment){ + if (timestamp < getLastms(idx)){return endFragment - 1;} + } + return endFragment; + } + + /// Returns the timestamp for the given key index in the given track index + uint64_t Meta::getTimeForKeyIndex(uint32_t idx, uint32_t keyIdx) const{ + DTSC::Keys keys(tracks.at(idx).keys); + return keys.getTime(keyIdx); + } + + /// Returns indice of the key containing timestamp, or last key if nowhere. + uint32_t Meta::getKeyIndexForTime(uint32_t idx, uint64_t timestamp) const{ + DTSC::Keys keys(tracks.at(idx).keys); + uint32_t firstKey = keys.getFirstValid(); + uint32_t endKey = keys.getEndValid(); + + for (size_t i = firstKey; i < endKey; i++){ + if (keys.getTime(i) + keys.getDuration(i) > timestamp){return i;} + } + return endKey; + } + + /// Returns the tiestamp for the given fragment index in the given track index. + uint64_t Meta::getTimeForFragmentIndex(uint32_t idx, uint32_t fragmentIdx) const{ + DTSC::Fragments fragments(tracks.at(idx).fragments); + DTSC::Keys keys(tracks.at(idx).keys); + return keys.getTime(fragments.getFirstKey(fragmentIdx)); + } + + /// Returns the part index for the given DTSC::Packet by timestamp. + /// Assumes the Packet is for the given track, and assumes the metadata and track data are not out + /// of sync. Works by looking up the key for the Packet's timestamp, then walking through the + /// parts until the time matches or exceeds the time of the Packet. Returns zero if the track + /// index is invalid or if the timestamp cannot be found. + uint32_t Meta::getPartIndex(const DTSC::Packet &pack, size_t idx) const{ + if (idx == INVALID_TRACK_ID){return 0;} + + uint32_t res = 0; + uint32_t keyIdx = getKeyIndexForTime(idx, pack.getTime()); + DTSC::Keys Keys(keys(idx)); + DTSC::Parts Parts(parts(idx)); + uint64_t currentTime = Keys.getTime(keyIdx); + res = Keys.getFirstPart(keyIdx); + size_t endPart = res + Keys.getParts(keyIdx); + for (size_t i = res; i < endPart; i++){ + if (currentTime >= pack.getTime()){return res;} + currentTime += Parts.getDuration(i); + res++; + } + return 0; + } + + /// Given the current page, check if the next page is available. Returns true if it is. + bool Meta::nextPageAvailable(uint32_t idx, size_t currentPage) const{ + const Util::RelAccX &pages = tracks.at(idx).pages; + for (size_t i = pages.getStartPos(); i + 1 < pages.getEndPos(); ++i){ + if (pages.getInt("firstkey", i) == currentPage){return pages.getInt("avail", i + 1);} + } + return false; + } + + /// Given a timestamp, returns the page number that timestamp can be found on. + /// If the timestamp is not available, returns the closest page number that is. + size_t Meta::getPageNumberForTime(uint32_t idx, uint64_t time) const{ + const Util::RelAccX &pages = tracks.at(idx).pages; + size_t res = pages.getStartPos(); + for (size_t i = pages.getStartPos(); i < pages.getEndPos(); ++i){ + if (pages.getInt("firsttime", i) > time){break;} + res = i; + } + return pages.getInt("firstkey", res); + } + + /// Given a key, returns the page number that timestamp can be found on. + /// If the key is not available, returns the closest page number that is. + size_t Meta::getPageNumberForKey(uint32_t idx, uint64_t keyNum) const{ + const Util::RelAccX &pages = tracks.at(idx).pages; + size_t res = pages.getStartPos(); + for (size_t i = pages.getStartPos(); i < pages.getEndPos(); ++i){ + if (pages.getInt("firstkey", i) > keyNum){break;} + res = i; + } + return pages.getInt("firstkey", res); + } + + Parts::Parts(const Util::RelAccX &_parts) : parts(_parts){ + sizeField = parts.getFieldData("size"); + durationField = parts.getFieldData("duration"); + offsetField = parts.getFieldData("offset"); + } + + size_t Parts::getFirstValid() const{return parts.getDeleted();} + size_t Parts::getEndValid() const{return parts.getEndPos();} + size_t Parts::getValidCount() const{return getEndValid() - getFirstValid();} + size_t Parts::getSize(size_t idx) const{return parts.getInt(sizeField, idx);} + uint64_t Parts::getDuration(size_t idx) const{return parts.getInt(durationField, idx);} + int64_t Parts::getOffset(size_t idx) const{return parts.getInt(offsetField, idx);} + + Keys::Keys(Util::RelAccX &_keys) : isConst(false), keys(_keys), cKeys(_keys){ + firstPartField = cKeys.getFieldData("firstpart"); + bposField = cKeys.getFieldData("bpos"); + durationField = cKeys.getFieldData("duration"); + numberField = cKeys.getFieldData("number"); + partsField = cKeys.getFieldData("parts"); + timeField = cKeys.getFieldData("time"); + sizeField = cKeys.getFieldData("size"); + } + + Keys::Keys(const Util::RelAccX &_keys) : isConst(true), keys(empty), cKeys(_keys){ + firstPartField = cKeys.getFieldData("firstpart"); + bposField = cKeys.getFieldData("bpos"); + durationField = cKeys.getFieldData("duration"); + numberField = cKeys.getFieldData("number"); + partsField = cKeys.getFieldData("parts"); + timeField = cKeys.getFieldData("time"); + sizeField = cKeys.getFieldData("size"); + } + + size_t Keys::getFirstValid() const{return cKeys.getDeleted();} + size_t Keys::getEndValid() const{return cKeys.getEndPos();} + size_t Keys::getValidCount() const{return getEndValid() - getFirstValid();} + + size_t Keys::getFirstPart(size_t idx) const{return cKeys.getInt(firstPartField, idx);} + size_t Keys::getBpos(size_t idx) const{return cKeys.getInt(bposField, idx);} + uint64_t Keys::getDuration(size_t idx) const{return cKeys.getInt(durationField, idx);} + size_t Keys::getNumber(size_t idx) const{return cKeys.getInt(numberField, idx);} + size_t Keys::getParts(size_t idx) const{return cKeys.getInt(partsField, idx);} + uint64_t Keys::getTime(size_t idx) const{return cKeys.getInt(timeField, idx);} + void Keys::setSize(size_t idx, size_t _size){ + if (isConst){return;} + keys.setInt(sizeField, _size, idx); + } + size_t Keys::getSize(size_t idx) const{return cKeys.getInt(sizeField, idx);} + + /// Returns the key number containing a given timestamp. + /// Returns the closest key number if the timestamp is not available. + size_t Keys::getNumForTime(uint64_t time) const{ + size_t res = getFirstValid(); + for (size_t i = getFirstValid(); i < getEndValid(); i++){ + if (getTime(i) > time){break;} + res = i; + } + return res; + } + + Fragments::Fragments(const Util::RelAccX &_fragments) : fragments(_fragments){} + size_t Fragments::getFirstValid() const{return fragments.getDeleted();} + size_t Fragments::getEndValid() const{return fragments.getEndPos();} + size_t Fragments::getValidCount() const{return getEndValid() - getFirstValid();} + uint64_t Fragments::getDuration(size_t idx) const{return fragments.getInt("duration", idx);} + size_t Fragments::getKeycount(size_t idx) const{return fragments.getInt("keys", idx);} + size_t Fragments::getFirstKey(size_t idx) const{return fragments.getInt("firstkey", idx);} + size_t Fragments::getSize(size_t idx) const{return fragments.getInt("size", idx);} +}// namespace DTSC diff --git a/lib/dtsc.h b/lib/dtsc.h index 6158da0a..2538cefa 100644 --- a/lib/dtsc.h +++ b/lib/dtsc.h @@ -2,9 +2,12 @@ /// Holds all headers for DDVTECH Stream Container parsing/generation. #pragma once +#include "defines.h" #include "json.h" +#include "shared_memory.h" #include "socket.h" #include "timing.h" +#include "util.h" #include #include #include @@ -28,6 +31,8 @@ namespace DTSC{ + extern uint64_t veryUglyJitterOverride; + ///\brief This enum holds all possible datatypes for DTSC packets. enum datatype{ 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_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}; /// 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 char *indice) const; Scan getMember(const char *indice, size_t ind_len) const; - void nullMember(const std::string &indice); - void nullMember(const char *indice, size_t ind_len); Scan getIndice(size_t num) const; std::string getIndiceName(size_t num) const; size_t getSize() const; @@ -104,7 +87,7 @@ namespace DTSC{ class Packet{ public: 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); virtual ~Packet(); void null(); @@ -113,9 +96,8 @@ namespace DTSC{ packType getVersion() const; void reInit(Socket::Connection &src); void reInit(const char *data_, unsigned int len, bool noCopy = false); - void genericFill(long long packTime, long long packOffset, long long packTrack, - const char *packData, long long packDataSize, uint64_t packBytePos, - bool isKeyframe, int64_t bootMsOffset = 0); + void genericFill(uint64_t packTime, int64_t packOffset, uint32_t packTrack, const char *packData, + size_t packDataSize, uint64_t packBytePos, bool isKeyframe); void appendData(const char *appendData, uint32_t appendLen); void getString(const char *identifier, char *&result, size_t &len) const; void getString(const char *identifier, std::string &result) const; @@ -129,7 +111,6 @@ namespace DTSC{ void setKeyFrame(bool kf); virtual uint64_t getTime() const; void setTime(uint64_t _time); - void nullMember(const std::string &memb); size_t getTrackId() const; char *getData() const; size_t getDataLen() const; @@ -139,7 +120,6 @@ namespace DTSC{ JSON::Value toJSON() const; std::string toSummary() const; Scan getScan() const; - Scan getScan(); protected: bool master; @@ -161,315 +141,358 @@ namespace DTSC{ : Packet(data_, len, noCopy){ timeOverride = reTime; } + ~RetimedPacket(){} virtual uint64_t getTime() const{return timeOverride;} protected: uint64_t timeOverride; }; - /// A simple structure used for ordering byte seek positions. - 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{ + class Parts{ public: - Ivec(); - Ivec(long long int iVec); - void setIvec(long long int iVec); - void setIvec(std::string iVec); - void setIvec(const char *iVec, int len); - long long int asInt(); - char *getData(); + Parts(const Util::RelAccX &_parts); + size_t getFirstValid() const; + size_t getEndValid() const; + size_t getValidCount() const; + size_t getSize(size_t idx) const; + uint64_t getDuration(size_t idx) const; + int64_t getOffset(size_t idx) const; private: - ///\brief Data storage for this initialization vector. - /// - /// - 8 bytes: MSB storage of the initialization vector. - char data[8]; + const Util::RelAccX &parts; + Util::RelAccXFieldData sizeField; + Util::RelAccXFieldData durationField; + Util::RelAccXFieldData offsetField; }; - /*LTS-END*/ - ///\brief Basic class for storage of data associated with single DTSC packets, a.k.a. parts. - class Part{ + class Keys{ public: - uint32_t getSize(); - void setSize(uint32_t newSize); - uint32_t getDuration(); - void setDuration(uint32_t newDuration); - uint32_t getOffset(); - void setOffset(uint32_t newOffset); - char *getData(); - void toPrettyString(std::ostream &str, int indent = 0); + Keys(Util::RelAccX &_keys); + Keys(const Util::RelAccX &_keys); + size_t getFirstValid() const; + size_t getEndValid() const; + size_t getValidCount() const; + size_t getFirstPart(size_t idx) const; + size_t getBpos(size_t idx) const; + 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: -#define PACKED_PART_SIZE 9 - ///\brief Data storage for this Part. - /// - /// - 3 bytes: MSB storage of the payload size of this packet in bytes. - /// - 3 bytes: MSB storage of the duration of this packet in milliseconds. - /// - 3 bytes: MSB storage of the presentation time offset of this packet in milliseconds. - char data[PACKED_PART_SIZE]; + bool isConst; + Util::RelAccX empty; + + Util::RelAccX &keys; + const Util::RelAccX &cKeys; + + 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. - /// - /// 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{ + class Fragments{ public: - unsigned long long getBpos(); - void setBpos(unsigned long long newBpos); - unsigned long getLength(); - void setLength(unsigned long newLength); - unsigned long getNumber(); - void setNumber(unsigned long newNumber); - unsigned short getParts(); - void setParts(unsigned short newParts); - unsigned long long getTime(); - void setTime(unsigned long long newTime); - char *getData(); - void toPrettyString(std::ostream &str, int indent = 0); + Fragments(const Util::RelAccX &_fragments); + size_t getFirstValid() const; + size_t getEndValid() const; + size_t getValidCount() const; + uint64_t getDuration(size_t idx) const; + size_t getKeycount(size_t idx) const; + size_t getFirstKey(size_t idx) const; + size_t getSize(size_t idx) const; private: -#define PACKED_KEY_SIZE 25 - ///\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]; + const Util::RelAccX &fragments; }; - ///\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{ public: - Track(); - Track(JSON::Value &trackRef); - Track(Scan &trackRef); - void clearParts(); + Util::RelAccX parts; + Util::RelAccX keys; + Util::RelAccX fragments; - inline operator bool() const{ - 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 fragments; - std::deque keys; - std::deque keySizes; - std::deque parts; - std::deque 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(); + Util::RelAccX pages; - std::string getIdentifier(); - 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(); + Util::RelAccX track; - private: - std::string cachedIdent; - std::deque fragInsertTime; + // Internal buffers so we don't always need to search for everything + Util::RelAccXFieldData trackIdField; + 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{ - /// \todo Make toJSON().toNetpacked() shorter public: - Meta(); - Meta(const DTSC::Packet &source); - Meta(JSON::Value &meta); - bool nextIsKey; + Meta(const std::string &_streamName, const DTSC::Scan &src); + Meta(const std::string &_streamName = "", bool master = true); + Meta(const std::string &_streamName, const std::string &fileName); - inline operator bool() const{// returns if the object contains valid meta data BY LOOKING AT vod/live FLAGS - return vod || live; - } - void reinit(const DTSC::Packet &source); - 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 selectedTracks = std::set()); - void send(Socket::Connection &conn, bool skipDynamic = false, - std::set selectedTracks = std::set()); - 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 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; - }; + ~Meta(); + void reInit(const std::string &_streamName, bool master = true); + void reInit(const std::string &_streamName, const std::string &fileName); + void reInit(const std::string &_streamName, const DTSC::Scan &src); - /// An iterator helper for easily iterating over the parts in a Fragment. - 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::iterator pIt; - std::deque::iterator kIt; - }; + void refresh(); - /// 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; - ~File(); - Meta &getMeta(); - long long int getLastReadPos(); - bool writeHeader(std::string &header, bool force = false); - long long int addHeader(std::string &header); - long int getBytePosEOF(); - long int getBytePos(); - bool reachedEOF(); - void seekNext(); - void parseNext(); - DTSC::Packet &getPacket(); - bool seek_time(unsigned int ms); - bool seek_time(unsigned int ms, unsigned int trackNo, bool forceSeek = false); - bool seek_bpos(int bpos); - void rewritePacket(std::string &newPacket, int bytePos); - void writePacket(std::string &newPacket); - void writePacket(JSON::Value &newPacket); - bool atKeyframe(); - void selectTracks(std::set &tracks); + + void setMaster(bool _master); + bool getMaster() const; + + void clear(); + + void minimalFrom(const Meta &src); + + bool trackLoaded(size_t idx) const; + bool trackValid(size_t idx) const; + size_t trackCount() const; + + size_t addCopy(size_t source); + size_t addDelayedTrack(size_t fragCount = DEFAULT_FRAGMENT_COUNT, size_t keyCount = DEFAULT_KEY_COUNT, + size_t partCount = DEFAULT_PART_COUNT, size_t pageCount = DEFAULT_PAGE_COUNT); + size_t addTrack(size_t fragCount = DEFAULT_FRAGMENT_COUNT, size_t keyCount = DEFAULT_KEY_COUNT, + size_t partCount = DEFAULT_PART_COUNT, size_t pageCount = DEFAULT_PAGE_COUNT, + bool setValid = true); + 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 getValidTracks(bool skipEmpty = false) const; + std::set 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 selectedTracks = std::set()) const; + void toFile(const std::string &fName) const; + void send(Socket::Connection &conn, bool skypDynamic = false, + std::set selectedTracks = std::set(), 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 tracks; + std::map tM; + + bool isMaster; + + char *streamMemBuf; + bool isMemBuf; + std::map tMemBuf; + std::map sizeMemBuf; private: - long int endPos; - void readHeader(int pos); - DTSC::Packet myPack; - Meta metadata; - std::map trackMapping; - long long int currtime; - long long int lastreadpos; - int currframe; - FILE *F; - unsigned long headerSize; - void *buffer; - bool created; - std::set currentPositions; - std::set selectedTracks; - }; - // FileWriter + // Internal buffers so we don't always need to search for everything + Util::RelAccXFieldData streamVodField; + Util::RelAccXFieldData streamLiveField; + Util::RelAccXFieldData streamSourceField; + Util::RelAccXFieldData streamBufferWindowField; + Util::RelAccXFieldData streamBootMsOffsetField; + Util::RelAccXFieldData streamMinimumFragmentDurationField; + 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 diff --git a/lib/dtscmeta.cpp b/lib/dtscmeta.cpp deleted file mode 100644 index 4218c987..00000000 --- a/lib/dtscmeta.cpp +++ /dev/null @@ -1,2123 +0,0 @@ -#include "bitfields.h" -#include "defines.h" -#include "dtsc.h" -#include -#include -#include -#include - -namespace DTSC{ - /// Default constructor for packets - sets a null pointer and invalid packet. - Packet::Packet(){ - data = NULL; - bufferLen = 0; - dataLen = 0; - master = false; - version = DTSC_INVALID; - } - - /// Copy constructor for packets, copies an existing packet with same noCopy flag as original. - Packet::Packet(const Packet &rhs){ - master = false; - bufferLen = 0; - data = NULL; - if (rhs.data && rhs.dataLen){ - reInit(rhs.data, rhs.dataLen, !rhs.master); - }else{ - null(); - } - } - - /// Data constructor for packets, either references or copies a packet from raw data. - Packet::Packet(const char *data_, unsigned int len, bool noCopy){ - master = false; - bufferLen = 0; - data = NULL; - reInit(data_, len, noCopy); - } - - /// This destructor clears frees the data pointer if the packet was not a reference. - Packet::~Packet(){ - if (master && data){free(data);} - } - - /// Copier for packets, copies an existing packet with same noCopy flag as original. - /// If going from copy to noCopy, this will free the data pointer first. - void Packet::operator=(const Packet &rhs){ - if (master && !rhs.master){null();} - if (rhs && rhs.data && rhs.dataLen){ - reInit(rhs.data, rhs.dataLen, !rhs.master); - }else{ - null(); - } - } - - /// Returns true if the packet is deemed valid, false otherwise. - /// Valid packets have a length of at least 8, known header type, and length equal to the length set in the header. - Packet::operator bool() const{ - if (!data){ - DONTEVEN_MSG("No data"); - return false; - } - if (dataLen < 8){ - VERYHIGH_MSG("Datalen < 8"); - return false; - } - if (version == DTSC_INVALID){ - VERYHIGH_MSG("No valid version"); - return false; - } - if (ntohl(((int *)data)[1]) + 8 > dataLen){ - VERYHIGH_MSG("Length mismatch"); - return false; - } - return true; - } - - /// Returns the recognized packet type. - /// This type is set by reInit and all constructors, and then just referenced from there on. - packType Packet::getVersion() const{return version;} - - /// Resets this packet back to the same state as if it had just been freshly constructed. - /// If needed, this frees the data pointer. - void Packet::null(){ - if (master && data){free(data);} - master = false; - data = NULL; - bufferLen = 0; - dataLen = 0; - version = DTSC_INVALID; - } - - /// Internally used resize function for when operating in copy mode and the internal buffer is too - /// small. It will only resize up, never down. - ///\param len The length th scale the buffer up to if necessary - void Packet::resize(size_t len){ - if (master && len > bufferLen){ - char *tmp = (char *)realloc(data, len); - if (tmp){ - data = tmp; - bufferLen = len; - }else{ - FAIL_MSG("Out of memory on parsing a packet"); - } - } - } - - void Packet::reInit(Socket::Connection &src){ - int sleepCount = 0; - null(); - int toReceive = 0; - while (src.connected()){ - if (!toReceive && src.Received().available(8)){ - if (src.Received().copy(2) != "DT"){ - WARN_MSG("Invalid DTSC Packet header encountered (%s)", src.Received().copy(4).c_str()); - break; - } - toReceive = Bit::btohl(src.Received().copy(8).data() + 4); - } - if (toReceive && src.Received().available(toReceive + 8)){ - std::string dataBuf = src.Received().remove(toReceive + 8); - reInit(dataBuf.data(), dataBuf.size()); - return; - } - if (!src.spool()){ - if (sleepCount++ > 150){ - WARN_MSG("Waiting for packet on connection timed out"); - return; - } - Util::wait(100); - } - } - } - - ///\brief Initializes a packet with new data - ///\param data_ The new data for the packet - ///\param len The length of the data pointed to by data_ - ///\param noCopy Determines whether to make a copy or not - void Packet::reInit(const char *data_, unsigned int len, bool noCopy){ - if (!data_){ - WARN_MSG("ReInit received a null pointer with len %d, nulling", len); - null(); - return; - } - if (!data_[0] && !data_[1] && !data_[2] && !data_[3]){ - null(); - return; - } - if (data_[0] != 'D' || data_[1] != 'T'){ - unsigned int twlen = len; - if (twlen > 20){twlen = 20;} - HIGH_MSG("ReInit received a pointer that didn't start with 'DT' but with %s (%u) - data " - "corruption?", - JSON::Value(std::string(data_, twlen)).toString().c_str(), len); - null(); - return; - } - if (len <= 0){len = ntohl(((int *)data_)[1]) + 8;} - // clear any existing controlled contents - if (master && noCopy){null();} - // set control flag to !noCopy - master = !noCopy; - // either copy the data, or only the pointer, depending on flag - if (noCopy){ - data = (char *)data_; - }else{ - resize(len); - memcpy(data, data_, len); - } - // check header type and store packet length - dataLen = len; - version = DTSC_INVALID; - if (len < 4){ - FAIL_MSG("ReInit received a packet with size < 4"); - return; - } - if (!memcmp(data, Magic_Packet2, 4)){version = DTSC_V2;} - if (!memcmp(data, Magic_Packet, 4)){version = DTSC_V1;} - if (!memcmp(data, Magic_Header, 4)){version = DTSC_HEAD;} - if (!memcmp(data, Magic_Command, 4)){version = DTCM;} - if (version == DTSC_INVALID){FAIL_MSG("ReInit received a packet with invalid header");} - } - - /// Re-initializes this Packet to contain a generic DTSC packet with the given data fields. - /// When given a NULL pointer, the data is reserved and memset to 0 - /// If given a NULL pointer and a zero size, an empty packet is created. - void Packet::genericFill(long long packTime, long long packOffset, long long packTrack, - const char *packData, long long packDataSize, uint64_t packBytePos, - bool isKeyframe, int64_t bootMsOffset){ - null(); - master = true; - // time and trackID are part of the 20-byte header. - // the container object adds 4 bytes (plus 2+namelen for each content, see below) - // offset, if non-zero, adds 9 bytes (integer type) and 8 bytes (2+namelen) - // bpos, if >= 0, adds 9 bytes (integer type) and 6 bytes (2+namelen) - // keyframe, if true, adds 9 bytes (integer type) and 10 bytes (2+namelen) - // bootmsoffset, if != 0, adds 9 bytes (integer type) and 5 bytes (2+namelen) - // data adds packDataSize+5 bytes (string type) and 6 bytes (2+namelen) - if (packData && packDataSize < 1){ - FAIL_MSG("Attempted to fill a packet with %lli bytes for timestamp %llu, track %llu!", - packDataSize, packTime, packTrack); - return; - } - unsigned int sendLen = 24 + (packOffset ? 17 : 0) + (packBytePos ? 15 : 0) + - (isKeyframe ? 19 : 0) + (bootMsOffset ? 14 : 0) + packDataSize + 11; - resize(sendLen); - // set internal variables - version = DTSC_V2; - dataLen = sendLen; - // write the first 20 bytes - memcpy(data, "DTP2", 4); - unsigned int tmpLong = htonl(sendLen - 8); - memcpy(data + 4, (char *)&tmpLong, 4); - tmpLong = htonl(packTrack); - memcpy(data + 8, (char *)&tmpLong, 4); - tmpLong = htonl((int)(packTime >> 32)); - memcpy(data + 12, (char *)&tmpLong, 4); - tmpLong = htonl((int)(packTime & 0xFFFFFFFF)); - memcpy(data + 16, (char *)&tmpLong, 4); - data[20] = 0xE0; // start container object - unsigned int offset = 21; - if (packOffset){ - memcpy(data + offset, "\000\006offset\001", 9); - tmpLong = htonl((int)(packOffset >> 32)); - memcpy(data + offset + 9, (char *)&tmpLong, 4); - tmpLong = htonl((int)(packOffset & 0xFFFFFFFF)); - memcpy(data + offset + 13, (char *)&tmpLong, 4); - offset += 17; - } - if (packBytePos){ - memcpy(data + offset, "\000\004bpos\001", 7); - tmpLong = htonl((int)(packBytePos >> 32)); - memcpy(data + offset + 7, (char *)&tmpLong, 4); - tmpLong = htonl((int)(packBytePos & 0xFFFFFFFF)); - memcpy(data + offset + 11, (char *)&tmpLong, 4); - offset += 15; - } - if (isKeyframe){ - memcpy(data + offset, "\000\010keyframe\001\000\000\000\000\000\000\000\001", 19); - offset += 19; - } - if (bootMsOffset){ - memcpy(data + offset, "\000\003bmo\001", 6); - tmpLong = htonl((int)(bootMsOffset >> 32)); - memcpy(data + offset + 6, (char *)&tmpLong, 4); - tmpLong = htonl((int)(bootMsOffset & 0xFFFFFFFF)); - memcpy(data + offset + 10, (char *)&tmpLong, 4); - offset += 14; - } - memcpy(data + offset, "\000\004data\002", 7); - tmpLong = htonl(packDataSize); - memcpy(data + offset + 7, (char *)&tmpLong, 4); - if (packData){ - memcpy(data + offset + 11, packData, packDataSize); - }else{ - memset(data + offset + 11, 0, packDataSize); - } - // finish container with 0x0000EE - memcpy(data + offset + 11 + packDataSize, "\000\000\356", 3); - } - - /// sets the keyframe byte. - void Packet::setKeyFrame(bool kf){ - uint32_t offset = 23; - while (data[offset] != 'd' && data[offset] != 'k' && data[offset] != 'K'){ - switch (data[offset]){ - case 'o': offset += 17; break; - case 'b': offset += 15; break; - default: FAIL_MSG("Unknown field: %c", data[offset]); - } - } - - if (data[offset] == 'k' || data[offset] == 'K'){ - data[offset] = (kf ? 'k' : 'K'); - data[offset + 16] = (kf ? 1 : 0); - }else{ - ERROR_MSG("Could not set keyframe - field not found!"); - } - } - - void Packet::appendData(const char *appendData, uint32_t appendLen){ - resize(dataLen + appendLen); - memcpy(data + dataLen - 3, appendData, appendLen); - memcpy(data + dataLen - 3 + appendLen, "\000\000\356", 3); // end container - dataLen += appendLen; - Bit::htobl(data + 4, Bit::btohl(data + 4) + appendLen); - uint32_t offset = getDataStringLenOffset(); - Bit::htobl(data + offset, Bit::btohl(data + offset) + appendLen); - } - - void Packet::appendNal(const char *appendData, uint32_t appendLen){ - if (appendLen == 0){return;} - - resize(dataLen + appendLen + 4); - Bit::htobl(data + dataLen - 3, appendLen); - memcpy(data + dataLen - 3 + 4, appendData, appendLen); - memcpy(data + dataLen - 3 + 4 + appendLen, "\000\000\356", 3); // end container - dataLen += appendLen + 4; - Bit::htobl(data + 4, Bit::btohl(data + 4) + appendLen + 4); - uint32_t offset = getDataStringLenOffset(); - Bit::htobl(data + offset, Bit::btohl(data + offset) + appendLen + 4); - - prevNalSize = appendLen; - } - - void Packet::upgradeNal(const char *appendData, uint32_t appendLen){ - if (appendLen == 0){return;} - uint64_t sizeOffset = dataLen - 3 - 4 - prevNalSize; - if (Bit::btohl(data + sizeOffset) != prevNalSize){ - FAIL_MSG("PrevNalSize state not correct"); - return; - } - resize(dataLen + appendLen); // Not + 4 as size bytes have already been written here. - Bit::htobl(data + sizeOffset, prevNalSize + appendLen); - prevNalSize += appendLen; - memcpy(data + dataLen - 3, appendData, appendLen); - memcpy(data + dataLen - 3 + appendLen, "\000\000\356", 3); // end container - dataLen += appendLen; - Bit::htobl(data + 4, Bit::btohl(data + 4) + appendLen); - uint32_t offset = getDataStringLenOffset(); - Bit::htobl(data + offset, Bit::btohl(data + offset) + appendLen); - } - - size_t Packet::getDataStringLen(){return Bit::btohl(data + getDataStringLenOffset());} - - /// Method can only be used when using internal functions to build the data. - size_t Packet::getDataStringLenOffset(){ - size_t offset = 23; - while (data[offset] != 'd'){ - switch (data[offset]){ - case 'o': offset += 17; break; - case 'b': offset += 15; break; - case 'k': offset += 19; break; - case 'K': offset += 19; break; - default: FAIL_MSG("Unknown field: %c", data[offset]); return -1; - } - } - return offset + 5; - } - - /// Helper function for skipping over whole DTSC parts - static char *skipDTSC(char *p, char *max){ - if (p + 1 >= max || p[0] == 0x00){ - return 0; // out of packet! 1 == error - } - if (p[0] == DTSC_INT){ - // int, skip 9 bytes to next value - return p + 9; - } - if (p[0] == DTSC_STR){ - if (p + 4 >= max){ - return 0; // out of packet! - } - return p + 5 + Bit::btohl(p + 1); - } - if (p[0] == DTSC_OBJ || p[0] == DTSC_CON){ - p++; - // object, scan contents - while (p < max && p[0] + p[1] != 0){// while not encountering 0x0000 (we assume 0x0000EE) - if (p + 2 >= max){ - return 0; // out of packet! - } - p += 2 + Bit::btohs(p); // skip size - // otherwise, search through the contents, if needed, and continue - p = skipDTSC(p, max); - if (!p){return 0;} - } - return p + 3; - } - if (p[0] == DTSC_ARR){ - p++; - // array, scan contents - while (p < max && p[0] + p[1] != 0){// while not encountering 0x0000 (we assume 0x0000EE) - // search through contents... - p = skipDTSC(p, max); - if (!p){return 0;} - } - return p + 3; // skip end marker - } - return 0; // out of packet! 1 == error - } - - ///\brief Retrieves a single parameter as a string - ///\param identifier The name of the parameter - ///\param result A location on which the string will be returned - ///\param len An integer in which the length of the string will be returned - void Packet::getString(const char *identifier, char *&result, size_t &len) const{ - getScan().getMember(identifier).getString(result, len); - } - - ///\brief Retrieves a single parameter as a string - ///\param identifier The name of the parameter - ///\param result The string in which to store the result - void Packet::getString(const char *identifier, std::string &result) const{ - result = getScan().getMember(identifier).asString(); - } - - ///\brief Retrieves a single parameter as an integer - ///\param identifier The name of the parameter - ///\param result The result is stored in this integer - void Packet::getInt(const char *identifier, uint64_t &result) const{ - result = getScan().getMember(identifier).asInt(); - } - - ///\brief Retrieves a single parameter as an integer - ///\param identifier The name of the parameter - ///\result The requested parameter as an integer - uint64_t Packet::getInt(const char *identifier) const{ - uint64_t result; - getInt(identifier, result); - return result; - } - - ///\brief Retrieves a single parameter as a boolean - ///\param identifier The name of the parameter - ///\param result The result is stored in this boolean - void Packet::getFlag(const char *identifier, bool &result) const{ - uint64_t result_; - getInt(identifier, result_); - result = result_; - } - - ///\brief Retrieves a single parameter as a boolean - ///\param identifier The name of the parameter - ///\result The requested parameter as a boolean - bool Packet::getFlag(const char *identifier) const{ - bool result; - getFlag(identifier, result); - return result; - } - - ///\brief Checks whether a parameter exists - ///\param identifier The name of the parameter - ///\result Whether the parameter exists or not - bool Packet::hasMember(const char *identifier) const{ - return getScan().getMember(identifier).getType() > 0; - } - - ///\brief Returns the timestamp of the packet. - ///\return The timestamp of this packet. - uint64_t Packet::getTime() const{ - if (version != DTSC_V2){ - if (!data){return 0;} - return getInt("time"); - } - return Bit::btohll(data + 12); - } - - void Packet::setTime(uint64_t _time){ - if (!master){ - INFO_MSG("Can't set the time for this packet, as it is not master."); - return; - } - Bit::htobll(data + 12, _time); - } - - void Packet::nullMember(const std::string &memb){ - if (!master){ - INFO_MSG("Can't null '%s' for this packet, as it is not master.", memb.c_str()); - return; - } - getScan().nullMember(memb); - } - - ///\brief Returns the track id of the packet. - ///\return The track id of this packet. - size_t Packet::getTrackId() const{ - if (version != DTSC_V2){return getInt("trackid");} - return Bit::btohl(data + 8); - } - - ///\brief Returns a pointer to the payload of this packet. - ///\return A pointer to the payload of this packet. - char *Packet::getData() const{return data;} - - ///\brief Returns the size of this packet. - ///\return The size of this packet. - size_t Packet::getDataLen() const{return dataLen;} - - ///\brief Returns the size of the payload of this packet. - ///\return The size of the payload of this packet. - size_t Packet::getPayloadLen() const{ - if (version == DTSC_V2){ - return dataLen - 20; - }else{ - return dataLen - 8; - } - } - - /// Returns a DTSC::Scan instance to the contents of this packet. - /// May return an invalid instance if this packet is invalid. - Scan Packet::getScan() const{ - if (!*this || !getDataLen() || !getPayloadLen() || getDataLen() <= getPayloadLen()){ - return Scan(); - } - return Scan(data + (getDataLen() - getPayloadLen()), getPayloadLen()); - } - - /// Returns a DTSC::Scan instance to the contents of this packet. - /// May return an invalid instance if this packet is invalid. - Scan Packet::getScan(){ - if (!*this || !getDataLen() || !getPayloadLen() || getDataLen() <= getPayloadLen()){ - return Scan(); - } - return Scan(data + (getDataLen() - getPayloadLen()), getPayloadLen()); - } - - ///\brief Converts the packet into a JSON value - ///\return A JSON::Value representation of this packet. - JSON::Value Packet::toJSON() const{ - JSON::Value result; - uint32_t i = 8; - if (getVersion() == DTSC_V1){JSON::fromDTMI(data, dataLen, i, result);} - if (getVersion() == DTSC_V2){JSON::fromDTMI2(data, dataLen, i, result);} - return result; - } - - std::string Packet::toSummary() const{ - std::stringstream out; - char *res = 0; - size_t len = 0; - getString("data", res, len); - out << getTrackId() << "@" << getTime() << ": " << len << " bytes"; - if (hasMember("keyframe")){out << " (keyframe)";} - return out.str(); - } - - /// Create an invalid DTSC::Scan object by default. - Scan::Scan(){ - p = 0; - len = 0; - } - - /// Create a DTSC::Scan object from memory pointer. - Scan::Scan(char *pointer, size_t length){ - p = pointer; - len = length; - } - - /// Returns whether the DTSC::Scan object contains valid data. - Scan::operator bool() const{return (p && len);} - - /// Returns an object representing the named indice of this object. - /// Returns an invalid object if this indice doesn't exist or this isn't an object type. - Scan Scan::getMember(const std::string &indice) const{ - return getMember(indice.data(), indice.size()); - } - - /// Returns an object representing the named indice of this object. - /// Returns an invalid object if this indice doesn't exist or this isn't an object type. - Scan Scan::getMember(const char *indice, const size_t ind_len) const{ - if (getType() != DTSC_OBJ && getType() != DTSC_CON){return Scan();} - char *i = p + 1; - // object, scan contents - while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume 0x0000EE) - if (i + 2 >= p + len){ - return Scan(); // out of packet! - } - uint16_t strlen = Bit::btohs(i); - i += 2; - if (ind_len == strlen && strncmp(indice, i, strlen) == 0){ - return Scan(i + strlen, len - (i - p)); - } - i = skipDTSC(i + strlen, p + len); - if (!i){return Scan();} - } - return Scan(); - } - - /// If this is an object type and contains the given indice/len, sets the indice name to all zeroes. - void Scan::nullMember(const std::string &indice){nullMember(indice.data(), indice.size());} - - /// If this is an object type and contains the given indice/len, sets the indice name to all zeroes. - void Scan::nullMember(const char *indice, const size_t ind_len){ - if (getType() != DTSC_OBJ && getType() != DTSC_CON){return;} - char *i = p + 1; - // object, scan contents - while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume 0x0000EE) - if (i + 2 >= p + len){ - return; // out of packet! - } - uint16_t strlen = Bit::btohs(i); - i += 2; - if (ind_len == strlen && strncmp(indice, i, strlen) == 0){ - memset(i, 0, strlen); - return; - } - i = skipDTSC(i + strlen, p + len); - if (!i){return;} - } - return; - } - - /// Returns an object representing the named indice of this object. - /// Returns an invalid object if this indice doesn't exist or this isn't an object type. - bool Scan::hasMember(const std::string &indice) const{ - return getMember(indice.data(), indice.size()); - } - - /// Returns whether an object representing the named indice of this object exists. - /// Returns false if this indice doesn't exist or this isn't an object type. - bool Scan::hasMember(const char *indice, const size_t ind_len) const{ - return getMember(indice, ind_len); - } - - /// Returns an object representing the named indice of this object. - /// Returns an invalid object if this indice doesn't exist or this isn't an object type. - Scan Scan::getMember(const char *indice) const{return getMember(indice, strlen(indice));} - - /// Returns the amount of indices if an array, the amount of members if an object, or zero otherwise. - size_t Scan::getSize() const{ - uint32_t arr_indice = 0; - if (getType() == DTSC_ARR){ - char *i = p + 1; - // array, scan contents - while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume 0x0000EE) - // search through contents... - arr_indice++; - i = skipDTSC(i, p + len); - if (!i){break;} - } - } - if (getType() == DTSC_OBJ || getType() == DTSC_CON){ - char *i = p + 1; - // object, scan contents - while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume 0x0000EE) - if (i + 2 >= p + len){ - return Scan(); // out of packet! - } - uint16_t strlen = Bit::btohs(i); - i += 2; - arr_indice++; - i = skipDTSC(i + strlen, p + len); - if (!i){break;} - } - } - return arr_indice; - } - - /// Returns an object representing the num-th indice of this array. - /// If not an array but an object, it returns the num-th member, instead. - /// Returns an invalid object if this indice doesn't exist or this isn't an array or object type. - Scan Scan::getIndice(size_t num) const{ - if (getType() == DTSC_ARR){ - char *i = p + 1; - unsigned int arr_indice = 0; - // array, scan contents - while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume 0x0000EE) - // search through contents... - if (arr_indice == num){ - return Scan(i, len - (i - p)); - }else{ - arr_indice++; - i = skipDTSC(i, p + len); - if (!i){return Scan();} - } - } - } - if (getType() == DTSC_OBJ || getType() == DTSC_CON){ - char *i = p + 1; - unsigned int arr_indice = 0; - // object, scan contents - while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume 0x0000EE) - if (i + 2 >= p + len){ - return Scan(); // out of packet! - } - unsigned int strlen = Bit::btohs(i); - i += 2; - if (arr_indice == num){ - return Scan(i + strlen, len - (i - p)); - }else{ - arr_indice++; - i = skipDTSC(i + strlen, p + len); - if (!i){return Scan();} - } - } - } - return Scan(); - } - - /// Returns the name of the num-th member of this object. - /// Returns an empty string on error or when not an object. - std::string Scan::getIndiceName(size_t num) const{ - if (getType() == DTSC_OBJ || getType() == DTSC_CON){ - char *i = p + 1; - unsigned int arr_indice = 0; - // object, scan contents - while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume 0x0000EE) - if (i + 2 >= p + len){ - return ""; // out of packet! - } - unsigned int strlen = Bit::btohs(i); - i += 2; - if (arr_indice == num){ - return std::string(i, strlen); - }else{ - arr_indice++; - i = skipDTSC(i + strlen, p + len); - if (!i){return "";} - } - } - } - return ""; - } - - /// Returns the first byte of this DTSC value, or 0 on error. - char Scan::getType() const{ - if (!p){return 0;} - return p[0]; - } - - /// Returns the boolean value of this DTSC value. - /// Numbers are compared to 0. - /// Strings are checked for non-zero length. - /// Objects and arrays are checked for content. - /// Returns false on error or in other cases. - bool Scan::asBool() const{ - switch (getType()){ - case DTSC_STR: return (p[1] | p[2] | p[3] | p[4]); - case DTSC_INT: return (asInt() != 0); - case DTSC_OBJ: - case DTSC_CON: - case DTSC_ARR: return (p[1] | p[2]); - default: return false; - } - } - - /// Returns the long long value of this DTSC number value. - /// Will convert string values to numbers, taking octal and hexadecimal types into account. - /// Illegal or invalid values return 0. - int64_t Scan::asInt() const{ - switch (getType()){ - case DTSC_INT: return Bit::btohll(p + 1); - case DTSC_STR: - char *str; - size_t strlen; - getString(str, strlen); - if (!strlen){return 0;} - return strtoll(str, 0, 0); - default: return 0; - } - } - - /// Returns the string value of this DTSC string value. - /// Uses getString internally, if a string. - /// Converts integer values to strings. - /// Returns an empty string on error. - std::string Scan::asString() const{ - switch (getType()){ - case DTSC_INT:{ - std::stringstream st; - st << asInt(); - return st.str(); - }break; - case DTSC_STR:{ - char *str; - size_t strlen; - getString(str, strlen); - return std::string(str, strlen); - }break; - } - return ""; - } - - /// Sets result to a pointer to the string, and strlen to the length of it. - /// Sets both to zero if this isn't a DTSC string value. - /// Attempts absolutely no conversion. - void Scan::getString(char *&result, size_t &strlen) const{ - if (getType() == DTSC_STR){ - result = p + 5; - strlen = Bit::btohl(p + 1); - return; - } - result = 0; - strlen = 0; - } - - /// Returns the DTSC scan object as a JSON value - /// Returns an empty object on error. - JSON::Value Scan::asJSON() const{ - JSON::Value result; - unsigned int i = 0; - JSON::fromDTMI(p, len, i, result); - return result; - } - - /// \todo Move this function to some generic area. Duplicate from json.cpp - static inline char hex2c(char c){ - if (c < 10){return '0' + c;} - if (c < 16){return 'A' + (c - 10);} - return '0'; - } - - /// \todo Move this function to some generic area. Duplicate from json.cpp - static std::string string_escape(const std::string val){ - std::stringstream out; - out << "\""; - for (size_t i = 0; i < val.size(); ++i){ - switch (val.data()[i]){ - case '"': out << "\\\""; break; - case '\\': out << "\\\\"; break; - case '\n': out << "\\n"; break; - case '\b': out << "\\b"; break; - case '\f': out << "\\f"; break; - case '\r': out << "\\r"; break; - case '\t': out << "\\t"; break; - default: - if (val.data()[i] < 32 || val.data()[i] > 126){ - out << "\\u00"; - out << hex2c((val.data()[i] >> 4) & 0xf); - out << hex2c(val.data()[i] & 0xf); - }else{ - out << val.data()[i]; - } - break; - } - } - out << "\""; - return out.str(); - } - - std::string Scan::toPrettyString(size_t indent) const{ - switch (getType()){ - case DTSC_STR:{ - uint32_t strlen = Bit::btohl(p + 1); - if (strlen > 250){ - std::stringstream ret; - ret << "\"" << strlen << " bytes of data\""; - return ret.str(); - } - return string_escape(asString()); - } - case DTSC_INT:{ - std::stringstream ret; - ret << asInt(); - return ret.str(); - } - case DTSC_OBJ: - case DTSC_CON:{ - std::stringstream ret; - ret << "{" << std::endl; - indent += 2; - char *i = p + 1; - bool first = true; - // object, scan contents - while (i[0] + i[1] != 0 && i < p + len){// while not encountering 0x0000 (we assume 0x0000EE) - if (i + 2 >= p + len){ - indent -= 2; - ret << std::string(indent, ' ') << "}//walked out of object here"; - return ret.str(); - } - if (!first){ret << "," << std::endl;} - first = false; - uint16_t strlen = Bit::btohs(i); - i += 2; - ret << std::string(indent, ' ') << "\"" << std::string(i, strlen) - << "\": " << Scan(i + strlen, len - (i - p)).toPrettyString(indent); - i = skipDTSC(i + strlen, p + len); - if (!i){ - indent -= 2; - ret << std::string(indent, ' ') << "}//could not locate next object"; - return ret.str(); - } - } - indent -= 2; - ret << std::endl << std::string(indent, ' ') << "}"; - return ret.str(); - } - case DTSC_ARR:{ - std::stringstream ret; - ret << "[" << std::endl; - indent += 2; - Scan tmpScan; - unsigned int i = 0; - bool first = true; - do{ - tmpScan = getIndice(i++); - if (tmpScan.getType()){ - if (!first){ret << "," << std::endl;} - first = false; - ret << std::string(indent, ' ') << tmpScan.toPrettyString(indent); - } - }while (tmpScan.getType()); - indent -= 2; - ret << std::endl << std::string(indent, ' ') << "]"; - return ret.str(); - } - default: return "Error"; - } - } - - /*LTS-START*/ - Ivec::Ivec(){setIvec(0);} - - Ivec::Ivec(long long int iVec){setIvec(iVec);} - - void Ivec::setIvec(long long int iVec){Bit::htobll(data, iVec);} - - void Ivec::setIvec(std::string iVec){ - memset(data, 0, 8); - memcpy(data, iVec.data(), std::min(8, (int)iVec.size())); - } - - void Ivec::setIvec(const char *iVec, int len){ - memset(data, 0, 8); - memcpy(data, iVec, std::min(8, len)); - } - - long long int Ivec::asInt(){return Bit::btohll(data);} - - char *Ivec::getData(){return data;} - /*LTS-END*/ - - ///\brief Returns the payloadsize of a part - uint32_t Part::getSize(){return Bit::btoh24(data);} - - ///\brief Sets the payloadsize of a part - void Part::setSize(uint32_t newSize){Bit::htob24(data, newSize);} - - ///\brief Returns the duration of a part - uint32_t Part::getDuration(){return Bit::btoh24(data + 3);} - - ///\brief Sets the duration of a part - void Part::setDuration(uint32_t newDuration){Bit::htob24(data + 3, newDuration);} - - ///\brief returns the offset of a part - /// Assumes the offset is actually negative if bit 0x800000 is set. - uint32_t Part::getOffset(){ - uint32_t ret = Bit::btoh24(data + 6); - if (ret & 0x800000){ - return ret | 0xff000000ul; - }else{ - return ret; - } - } - - ///\brief Sets the offset of a part - void Part::setOffset(uint32_t newOffset){Bit::htob24(data + 6, newOffset);} - - ///\brief Returns the data of a part - char *Part::getData(){return data;} - - ///\brief Converts a part to a human readable string - ///\param str The stringstream to append to - ///\param indent the amount of indentation needed - void Part::toPrettyString(std::ostream &str, int indent){ - str << std::string(indent, ' ') << "Part: Size(" << getSize() << "), Dur(" << getDuration() - << "), Offset(" << getOffset() << ")" << std::endl; - } - - ///\brief Returns the byteposition of a keyframe - unsigned long long Key::getBpos(){return Bit::btohll(data);} - - void Key::setBpos(unsigned long long newBpos){Bit::htobll(data, newBpos);} - - unsigned long Key::getLength(){return Bit::btoh24(data + 8);} - - void Key::setLength(unsigned long newLength){Bit::htob24(data + 8, newLength);} - - ///\brief Returns the number of a keyframe - unsigned long Key::getNumber(){return Bit::btohl(data + 11);} - - ///\brief Sets the number of a keyframe - void Key::setNumber(unsigned long newNumber){Bit::htobl(data + 11, newNumber);} - - ///\brief Returns the number of parts of a keyframe - unsigned short Key::getParts(){return Bit::btohs(data + 15);} - - ///\brief Sets the number of parts of a keyframe - void Key::setParts(unsigned short newParts){Bit::htobs(data + 15, newParts);} - - ///\brief Returns the timestamp of a keyframe - unsigned long long Key::getTime(){return Bit::btohll(data + 17);} - - ///\brief Sets the timestamp of a keyframe - void Key::setTime(unsigned long long newTime){Bit::htobll(data + 17, newTime);} - - ///\brief Returns the data of this keyframe struct - char *Key::getData(){return data;} - - ///\brief Converts a keyframe to a human readable string - ///\param str The stringstream to append to - ///\param indent the amount of indentation needed - void Key::toPrettyString(std::ostream &str, int indent){ - str << std::string(indent, ' ') << "Key " << getNumber() << ": Pos(" << getBpos() << "), Dur(" - << getLength() << "), Parts(" << getParts() << "), Time(" << getTime() << ")" << std::endl; - } - - ///\brief Returns the duration of this fragment - unsigned long Fragment::getDuration(){return Bit::btohl(data);} - - ///\brief Sets the duration of this fragment - void Fragment::setDuration(unsigned long newDuration){Bit::htobl(data, newDuration);} - - ///\brief Returns the length of this fragment - char Fragment::getLength(){return data[4];} - - ///\brief Sets the length of this fragment - void Fragment::setLength(char newLength){data[4] = newLength;} - - ///\brief Returns the number of the first keyframe in this fragment - unsigned long Fragment::getNumber(){return Bit::btohl(data + 5);} - - ///\brief Sets the number of the first keyframe in this fragment - void Fragment::setNumber(unsigned long newNumber){Bit::htobl(data + 5, newNumber);} - - ///\brief Returns the size of a fragment - unsigned long Fragment::getSize(){return Bit::btohl(data + 9);} - - ///\brief Sets the size of a fragement - void Fragment::setSize(unsigned long newSize){Bit::htobl(data + 9, newSize);} - - ///\brief Returns thte data of this fragment structure - char *Fragment::getData(){return data;} - - ///\brief Converts a fragment to a human readable string - ///\param str The stringstream to append to - ///\param indent the amount of indentation needed - void Fragment::toPrettyString(std::ostream &str, int indent){ - str << std::string(indent, ' ') << "Fragment " << getNumber() << ": Dur(" << getDuration() - << "), Len(" << (int)getLength() << "), Size(" << getSize() << ")" << std::endl; - } - - ///\brief Constructs an empty track - Track::Track(){ - trackID = 0; - firstms = 0; - lastms = 0; - bps = 0; - max_bps = 0; - missedFrags = 0; - rate = 0; - size = 0; - channels = 0; - width = 0; - height = 0; - fpks = 0; - minKeepAway = 0; - } - - ///\brief Constructs a track from a JSON::Value - Track::Track(JSON::Value &trackRef){ - if (trackRef.isMember("fragments") && trackRef["fragments"].isString()){ - Fragment *tmp = (Fragment *)trackRef["fragments"].asStringRef().data(); - fragments = std::deque(tmp, tmp + (trackRef["fragments"].asStringRef().size() / PACKED_FRAGMENT_SIZE)); - } - if (trackRef.isMember("keys") && trackRef["keys"].isString()){ - Key *tmp = (Key *)trackRef["keys"].asStringRef().data(); - keys = std::deque(tmp, tmp + (trackRef["keys"].asStringRef().size() / PACKED_KEY_SIZE)); - } - if (trackRef.isMember("parts") && trackRef["parts"].isString()){ - Part *tmp = (Part *)trackRef["parts"].asStringRef().data(); - parts = std::deque(tmp, tmp + (trackRef["parts"].asStringRef().size() / 9)); - } - /*LTS-START*/ - if (trackRef.isMember("ivecs") && trackRef["ivecs"].isString()){ - Ivec *tmp = (Ivec *)trackRef["ivecs"].asString().data(); - ivecs = std::deque(tmp, tmp + (trackRef["ivecs"].asString().size() / 8)); - } - /*LTS-END*/ - trackID = trackRef["trackid"].asInt(); - firstms = trackRef["firstms"].asInt(); - lastms = trackRef["lastms"].asInt(); - bps = trackRef["bps"].asInt(); - max_bps = trackRef["maxbps"].asInt(); - missedFrags = trackRef["missed_frags"].asInt(); - codec = trackRef["codec"].asStringRef(); - type = trackRef["type"].asStringRef(); - init = trackRef["init"].asStringRef(); - if (trackRef.isMember("lang") && trackRef["lang"].asStringRef().size()){ - lang = trackRef["lang"].asStringRef(); - } - if (type == "audio"){ - rate = trackRef["rate"].asInt(); - size = trackRef["size"].asInt(); - channels = trackRef["channels"].asInt(); - } - if (type == "video"){ - width = trackRef["width"].asInt(); - height = trackRef["height"].asInt(); - fpks = trackRef["fpks"].asInt(); - } - if (trackRef.isMember("keysizes") && trackRef["keysizes"].isString()){ - std::string tmp = trackRef["keysizes"].asStringRef(); - for (unsigned int i = 0; i < tmp.size(); i += 4){ - keySizes.push_back((((long unsigned)tmp[i]) << 24) | (((long unsigned)tmp[i + 1]) << 16) | - (((long unsigned int)tmp[i + 2]) << 8) | tmp[i + 3]); - } - } - if (trackRef.isMember("keepaway") && trackRef["keepaway"].isInt()){ - minKeepAway = trackRef["keepaway"].asInt(); - }else{ - minKeepAway = 0; - } - } - - ///\brief Constructs a track from a JSON::Value - Track::Track(Scan &trackRef){ - if (trackRef.getMember("fragments").getType() == DTSC_STR){ - char *tmp = 0; - size_t tmplen = 0; - trackRef.getMember("fragments").getString(tmp, tmplen); - fragments = std::deque((Fragment *)tmp, ((Fragment *)tmp) + (tmplen / PACKED_FRAGMENT_SIZE)); - } - if (trackRef.getMember("keys").getType() == DTSC_STR){ - char *tmp = 0; - size_t tmplen = 0; - trackRef.getMember("keys").getString(tmp, tmplen); - keys = std::deque((Key *)tmp, ((Key *)tmp) + (tmplen / PACKED_KEY_SIZE)); - } - if (trackRef.getMember("parts").getType() == DTSC_STR){ - char *tmp = 0; - size_t tmplen = 0; - trackRef.getMember("parts").getString(tmp, tmplen); - parts = std::deque((Part *)tmp, ((Part *)tmp) + (tmplen / 9)); - } - /*LTS-START*/ - if (trackRef.getMember("ivecs").getType() == DTSC_STR){ - char *tmp = 0; - size_t tmplen = 0; - trackRef.getMember("ivecs").getString(tmp, tmplen); - ivecs = std::deque((Ivec *)tmp, ((Ivec *)tmp) + (tmplen / 8)); - } - /*LTS-END*/ - trackID = trackRef.getMember("trackid").asInt(); - firstms = trackRef.getMember("firstms").asInt(); - lastms = trackRef.getMember("lastms").asInt(); - bps = trackRef.getMember("bps").asInt(); - max_bps = trackRef.getMember("maxbps").asInt(); - missedFrags = trackRef.getMember("missed_frags").asInt(); - codec = trackRef.getMember("codec").asString(); - type = trackRef.getMember("type").asString(); - init = trackRef.getMember("init").asString(); - if (trackRef.getMember("lang")){lang = trackRef.getMember("lang").asString();} - if (type == "audio"){ - rate = trackRef.getMember("rate").asInt(); - size = trackRef.getMember("size").asInt(); - channels = trackRef.getMember("channels").asInt(); - } - if (type == "video"){ - width = trackRef.getMember("width").asInt(); - height = trackRef.getMember("height").asInt(); - fpks = trackRef.getMember("fpks").asInt(); - } - if (trackRef.getMember("keysizes").getType() == DTSC_STR){ - char *tmp = 0; - size_t tmplen = 0; - trackRef.getMember("keysizes").getString(tmp, tmplen); - for (unsigned int i = 0; i < tmplen; i += 4){ - keySizes.push_back((((long unsigned)tmp[i]) << 24) | (((long unsigned)tmp[i + 1]) << 16) | - (((long unsigned int)tmp[i + 2]) << 8) | tmp[i + 3]); - } - } - if (trackRef.getMember("keepaway").getType() == DTSC_INT){ - minKeepAway = trackRef.getMember("keepaway").asInt(); - }else{ - minKeepAway = 0; - } - } - - ///\brief Updates a track and its metadata given new packet properties. - /// Will also insert keyframes on non-video tracks, and creates fragments - /*LTS - void Track::update(long long packTime, long long packOffset, long long packDataSize, uint64_t - packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size){LTS*/ - void Track::update(long long packTime, long long packOffset, long long packDataSize, uint64_t packBytePos, - bool isKeyframe, long long packSendSize, unsigned long segment_size, const char *iVec){ - if ((unsigned long long)packTime < lastms){ - static bool warned = false; - if (!warned){ - ERROR_MSG("Received packets for track %u in wrong order (%lld < %llu) - ignoring! Further " - "messages on HIGH level.", - trackID, packTime, lastms); - warned = true; - }else{ - HIGH_MSG("Received packets for track %u in wrong order (%lld < %llu) - ignoring! Further " - "messages on HIGH level.", - trackID, packTime, lastms); - } - return; - } - Part newPart; - newPart.setSize(packDataSize); - newPart.setOffset(packOffset); - if (parts.size()){ - parts[parts.size() - 1].setDuration(packTime - lastms); - newPart.setDuration(packTime - lastms); - }else{ - newPart.setDuration(0); - } - parts.push_back(newPart); - lastms = packTime; - if (isKeyframe || !keys.size() || - (type != "video" && packTime >= AUDIO_KEY_INTERVAL && - packTime - (unsigned long long)keys[keys.size() - 1].getTime() >= AUDIO_KEY_INTERVAL)){ - Key newKey; - newKey.setTime(packTime); - newKey.setParts(0); - newKey.setLength(0); - if (keys.size()){ - newKey.setNumber(keys[keys.size() - 1].getNumber() + 1); - keys[keys.size() - 1].setLength(packTime - keys[keys.size() - 1].getTime()); - }else{ - newKey.setNumber(1); - } - if (packBytePos){// For VoD - newKey.setBpos(packBytePos); - }else{ - newKey.setBpos(0); - } - /*LTS-START*/ - if (iVec){ - Ivec newIvec; - newIvec.setIvec(iVec, 8); - ivecs.push_back(newIvec); - } - /*LTS-END*/ - keys.push_back(newKey); - keySizes.push_back(0); - firstms = keys[0].getTime(); - if (!fragments.size() || ((unsigned long long)packTime > segment_size && - (unsigned long long)packTime - segment_size >= - (unsigned long long)getKey(fragments.rbegin()->getNumber()).getTime())){ - // new fragment - Fragment newFrag; - newFrag.setDuration(0); - newFrag.setLength(1); - newFrag.setNumber(keys[keys.size() - 1].getNumber()); - if (fragments.size()){ - fragments[fragments.size() - 1].setDuration( - packTime - getKey(fragments[fragments.size() - 1].getNumber()).getTime()); - uint64_t totalBytes = 0; - uint64_t totalDuration = 0; - for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++){ - totalBytes += it->getSize(); - totalDuration += it->getDuration(); - } - bps = totalDuration ? (totalBytes * 1000) / totalDuration : 0; - max_bps = std::max(max_bps, (int)((fragments.rbegin()->getSize() * 1000) / - fragments.rbegin()->getDuration())); - } - newFrag.setDuration(0); - newFrag.setSize(0); - fragments.push_back(newFrag); - // We set the insert time lastms-firstms in the future, to prevent unstable playback - fragInsertTime.push_back(Util::bootSecs() + ((lastms - firstms) / 1000)); - }else{ - Fragment &lastFrag = fragments[fragments.size() - 1]; - lastFrag.setLength(lastFrag.getLength() + 1); - } - } - keys.rbegin()->setParts(keys.rbegin()->getParts() + 1); - (*keySizes.rbegin()) += packSendSize; - fragments.rbegin()->setSize(fragments.rbegin()->getSize() + packDataSize); - } - - void Track::clearParts(){ - while (fragments.size() > 1){removeFirstKey();} - } - - /// Removes the first buffered key, including any fragments it was part of - void Track::removeFirstKey(){ - HIGH_MSG("Erasing key %d:%lu", trackID, keys[0].getNumber()); - // remove all parts of this key - for (int i = 0; i < keys[0].getParts(); i++){parts.pop_front();} - // remove the key itself - keys.pop_front(); - keySizes.pop_front(); - // update firstms - firstms = keys[0].getTime(); - // delete any fragments no longer fully buffered - while (fragments.size() && keys.size() && fragments[0].getNumber() < keys[0].getNumber()){ - fragments.pop_front(); - fragInsertTime.pop_front(); - // and update the missed fragment counter - ++missedFrags; - } - } - - /// Returns the amount of whole seconds since the first fragment was inserted into the buffer. - /// This assumes playback from the start of the buffer at time of insert, meaning that - /// the time is offset by that difference. E.g.: if a buffer is 50s long, the newest fragment - /// will have a value of 0 until 50s have passed, after which it will increase at a rate of - /// 1 per second. - uint32_t Track::secsSinceFirstFragmentInsert(){ - uint32_t bs = Util::bootSecs(); - if (bs > fragInsertTime.front()){ - return bs - fragInsertTime.front(); - }else{ - return 0; - } - } - - void Track::finalize(){ - keys.rbegin()->setLength(lastms - keys.rbegin()->getTime() + parts.rbegin()->getDuration()); - } - - /// Returns the duration in ms of the longest-duration fragment. - uint32_t Track::biggestFragment(){ - uint32_t ret = 0; - for (unsigned int i = 0; i < fragments.size(); i++){ - if (fragments[i].getDuration() > ret){ret = fragments[i].getDuration();} - } - return ret; - } - - ///\brief Returns a key given its number, or an empty key if the number is out of bounds - Key &Track::getKey(unsigned int keyNum){ - static Key empty; - if (!keys.size() || keyNum < keys[0].getNumber()){return empty;} - if ((keyNum - keys[0].getNumber()) > keys.size()){return empty;} - return keys[keyNum - keys[0].getNumber()]; - } - - ///\brief Returns a fragment given its number, or an empty fragment if the number is out of bounds - Fragment &Track::getFrag(unsigned int fragNum){ - static Fragment empty; - if (!fragments.size() || fragNum < fragments[0].getNumber() || fragNum > fragments.rbegin()->getNumber()){ - return empty; - } - for (std::deque::iterator it = fragments.begin(); it != fragments.end(); ++it){ - if (fragNum >= it->getNumber() && fragNum <= it->getNumber() + it->getLength()){ - return *it; - } - } - return empty; - } - - /// Returns the number of the key containing timestamp, or last key if nowhere. - unsigned int Track::timeToKeynum(unsigned int timestamp){ - unsigned int result = 0; - for (std::deque::iterator it = keys.begin(); it != keys.end(); it++){ - if (it->getTime() > timestamp){break;} - result = it->getNumber(); - } - return result; - } - - /// Gets indice of the fragment containing timestamp, or last fragment if nowhere. - uint32_t Track::timeToFragnum(uint64_t timestamp){ - uint32_t i = 0; - for (std::deque::iterator it = fragments.begin(); it != fragments.end(); ++it){ - if (timestamp < getKey(it->getNumber()).getTime() + it->getDuration()){return i;} - ++i; - } - return fragments.size() - 1; - } - - ///\brief Resets a track, clears all meta values - void Track::reset(){ - fragments.clear(); - fragInsertTime.clear(); - parts.clear(); - keySizes.clear(); - keys.clear(); - bps = 0; - max_bps = 0; - firstms = 0; - lastms = 0; - } - - ///\brief Creates an empty meta object - Meta::Meta(){ - nextIsKey = false; - vod = false; - live = false; - version = DTSH_VERSION; - moreheader = 0; - merged = false; - bufferWindow = 0; - bootMsOffset = 0; - } - - Meta::Meta(const DTSC::Packet &source){reinit(source);} - - void Meta::reinit(const DTSC::Packet &source){ - tracks.clear(); - vod = source.getFlag("vod"); - live = source.getFlag("live"); - version = source.getInt("version"); - merged = source.getFlag("merged"); - bufferWindow = source.getInt("buffer_window"); - moreheader = source.getInt("moreheader"); - bootMsOffset = source.getInt("bootoffset"); - source.getString("source", sourceURI); - if (source.getScan().hasMember("inputlocalvars")){ - inputLocalVars = source.getScan().getMember("inputlocalvars").asJSON(); - } - Scan tmpTracks = source.getScan().getMember("tracks"); - unsigned int num = 0; - Scan tmpTrack; - do{ - tmpTrack = tmpTracks.getIndice(num); - if (tmpTrack.asBool()){ - unsigned int trackId = tmpTrack.getMember("trackid").asInt(); - if (trackId){tracks[trackId] = Track(tmpTrack);} - num++; - } - }while (tmpTrack.asBool()); - } - - ///\brief Creates a meta object from a JSON::Value - Meta::Meta(JSON::Value &meta){ - vod = meta.isMember("vod") && meta["vod"]; - live = meta.isMember("live") && meta["live"]; - sourceURI = meta.isMember("source") ? meta["source"].asStringRef() : ""; - version = meta.isMember("version") ? meta["version"].asInt() : 0; - bootMsOffset = meta.isMember("bootoffset") ? meta["bootoffset"].asInt() : 0; - merged = meta.isMember("merged") && meta["merged"]; - bufferWindow = 0; - if (meta.isMember("buffer_window")){bufferWindow = meta["buffer_window"].asInt();} - // for (JSON::ObjIter it = meta["tracks"].ObjBegin(); it != meta["tracks"].ObjEnd(); it++){ - jsonForEach(meta["tracks"], it){ - if ((*it)["trackid"].asInt()){tracks[(*it)["trackid"].asInt()] = Track((*it));} - } - if (meta.isMember("moreheader")){ - moreheader = meta["moreheader"].asInt(); - }else{ - moreheader = 0; - } - } - - ///\brief Updates a meta object given a JSON::Value - void Meta::update(JSON::Value &pack, unsigned long segment_size){ - /*LTS - update(pack["time"].asInt(), pack.isMember("offset")?pack["offset"].asInt():0, - pack["trackid"].asInt(), pack["data"].asStringRef().size(), - pack.isMember("bpos")?pack["bpos"].asInt():0, pack.isMember("keyframe"), pack.packedSize(), - segment_size); LTS*/ - update(pack["time"].asInt(), pack.isMember("offset") ? pack["offset"].asInt() : 0, - pack["trackid"].asInt(), pack["data"].asStringRef().size(), - pack.isMember("bpos") ? pack["bpos"].asInt() : 0, pack.isMember("keyframe"), pack.packedSize(), - segment_size, pack.isMember("ivec") ? pack["ivec"].asStringRef().data() : 0); - } - - ///\brief Updates a meta object given a DTSC::Packet - void Meta::update(const DTSC::Packet &pack, unsigned long segment_size){ - char *data; - size_t dataLen; - pack.getString("data", data, dataLen); - char *ivec; - size_t ivecLen; - pack.getString("ivec", ivec, ivecLen); - /*LTS - update(pack.getTime(), pack.hasMember("offset")?pack.getInt("offset"):0, pack.getTrackId(), - dataLen, pack.hasMember("bpos")?pack.getInt("bpos"):0, pack.hasMember("keyframe"), - pack.getDataLen(), segment_size); LTS*/ - update(pack.getTime(), pack.hasMember("offset") ? pack.getInt("offset") : 0, pack.getTrackId(), - dataLen, pack.hasMember("bpos") ? pack.getInt("bpos") : 0, pack.hasMember("keyframe"), - pack.getDataLen(), segment_size, ivecLen ? ivec : 0); - if (!bootMsOffset && pack.hasMember("bmo")){bootMsOffset = pack.getInt("bmo");} - } - - ///\brief Updates a meta object given a DTSC::Packet with byte position override. - void Meta::updatePosOverride(DTSC::Packet &pack, uint64_t bpos){ - char *data; - size_t dataLen; - pack.getString("data", data, dataLen); - /*LTS - update(pack.getTime(), pack.hasMember("offset")?pack.getInt("offset"):0, pack.getTrackId(), - dataLen, bpos, pack.hasMember("keyframe"), pack.getDataLen()); LTS*/ - char *ivec; - size_t ivecLen; - pack.getString("ivec", ivec, ivecLen); - update(pack.getTime(), pack.hasMember("offset") ? pack.getInt("offset") : 0, pack.getTrackId(), - dataLen, bpos, pack.hasMember("keyframe"), pack.getDataLen(), 1900, ivecLen ? ivec : 0); - } - - void Meta::update(long long packTime, long long packOffset, long long packTrack, - long long packDataSize, uint64_t packBytePos, bool isKeyframe, - long long packSendSize, unsigned long segment_size, const char *ivec){ - if (nextIsKey){ - isKeyframe = true; - nextIsKey = false; - } - DONTEVEN_MSG("Updating meta with: t=%lld, o=%lld, s=%lld, t=%lld, p=%lld", packTime, packOffset, - packDataSize, packTrack, packBytePos); - if (!packSendSize){ - // time and trackID are part of the 20-byte header. - // the container object adds 4 bytes (plus 2+namelen for each content, see below) - // offset, if non-zero, adds 9 bytes (integer type) and 8 bytes (2+namelen) - // bpos, if >= 0, adds 9 bytes (integer type) and 6 bytes (2+namelen) - // keyframe, if true, adds 9 bytes (integer type) and 10 bytes (2+namelen) - // data adds packDataSize+5 bytes (string type) and 6 bytes (2+namelen) - packSendSize = 24 + (packOffset ? 17 : 0) + (packBytePos ? 15 : 0) + (isKeyframe ? 19 : 0) + - packDataSize + 11; - } - if (vod != (packBytePos > 0)){ - INFO_MSG("Changing stream from %s to %s (bPos=%lld)", vod ? "VoD" : "live", - packBytePos ? "Vod" : "live", packBytePos); - } - vod = (packBytePos > 0); - live = !vod; - EXTREME_MSG("Updating meta with %lld@%lld+%lld", packTrack, packTime, packOffset); - if (packTrack > 0 && tracks.count(packTrack)){ - tracks[packTrack].update(packTime, packOffset, packDataSize, packBytePos, isKeyframe, - packSendSize, segment_size, ivec); - } - } - - /// Returns a reference to the first video track, or the first track. - /// Beware: returns a reference to invalid memory if there are no tracks! - /// Will print a WARN-level message if this is the case. - Track &Meta::mainTrack(){ - if (!tracks.size()){ - WARN_MSG("Returning nonsense reference - crashing is likely"); - return tracks.begin()->second; - } - for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++){ - if (it->second.type == "video"){return it->second;} - } - return tracks.begin()->second; - } - - /// Returns 0 if there are no tracks, otherwise calls mainTrack().biggestFragment(). - uint32_t Meta::biggestFragment(){ - if (!tracks.size()){return 0;} - return mainTrack().biggestFragment(); - } - - ///\brief Converts a track to a human readable string - ///\param str The stringstream to append to - ///\param indent the amount of indentation needed - ///\param verbosity How verbose the output needs to be - void Track::toPrettyString(std::ostream &str, int indent, int verbosity){ - str << std::string(indent, ' ') << "Track " << getWritableIdentifier() << std::endl; - str << std::string(indent + 2, ' ') << "ID: " << trackID << std::endl; - str << std::string(indent + 2, ' ') << "Firstms: " << firstms << std::endl; - str << std::string(indent + 2, ' ') << "Lastms: " << lastms << std::endl; - str << std::string(indent + 2, ' ') << "Bps: " << bps << std::endl; - str << std::string(indent + 2, ' ') << "Peak bps: " << max_bps << std::endl; - if (missedFrags){ - str << std::string(indent + 2, ' ') << "missedFrags: " << missedFrags << std::endl; - } - str << std::string(indent + 2, ' ') << "Codec: " << codec << std::endl; - str << std::string(indent + 2, ' ') << "Type: " << type << std::endl; - str << std::string(indent + 2, ' ') << "Init: "; - for (unsigned int i = 0; i < init.size(); ++i){ - str << std::hex << std::setw(2) << std::setfill('0') << (int)init[i]; - } - str << std::dec << std::endl; - if (lang.size()){str << std::string(indent + 2, ' ') << "Language: " << lang << std::endl;} - if (type == "audio"){ - str << std::string(indent + 2, ' ') << "Rate: " << rate << std::endl; - str << std::string(indent + 2, ' ') << "Size: " << size << std::endl; - str << std::string(indent + 2, ' ') << "Channel: " << channels << std::endl; - }else if (type == "video"){ - str << std::string(indent + 2, ' ') << "Width: " << width << std::endl; - str << std::string(indent + 2, ' ') << "Height: " << height << std::endl; - str << std::string(indent + 2, ' ') << "Fpks: " << fpks << std::endl; - } - str << std::string(indent + 2, ' ') << "Fragments: " << fragments.size() << std::endl; - if (verbosity & 0x01){ - for (unsigned int i = 0; i < fragments.size(); i++){ - fragments[i].toPrettyString(str, indent + 4); - } - } - str << std::string(indent + 2, ' ') << "Keys: " << keys.size() << std::endl; - if (verbosity & 0x02){ - for (unsigned int i = 0; i < keys.size(); i++){keys[i].toPrettyString(str, indent + 4);} - } - str << std::string(indent + 2, ' ') << "KeySizes: " << keySizes.size() << std::endl; - if (keySizes.size() && verbosity & 0x02){ - for (unsigned int i = 0; i < keySizes.size(); i++){ - str << std::string(indent + 4, ' ') << "[" << i << "] " << keySizes[i] << std::endl; - } - } - str << std::string(indent + 2, ' ') << "Parts: " << parts.size() << std::endl; - if (verbosity & 0x04){ - for (unsigned int i = 0; i < parts.size(); i++){parts[i].toPrettyString(str, indent + 4);} - } - } - - ///\brief Converts a short to a char* - char *convertShort(short input){ - static char result[2]; - result[0] = (input >> 8) & 0xFF; - result[1] = (input)&0xFF; - return result; - } - - ///\brief Converts an integer to a char* - char *convertInt(int input){ - static char result[4]; - result[0] = (input >> 24) & 0xFF; - result[1] = (input >> 16) & 0xFF; - result[2] = (input >> 8) & 0xFF; - result[3] = (input)&0xFF; - return result; - } - - ///\brief Converts a long long to a char* - char *convertLongLong(long long int input){ - static char result[8]; - result[0] = (input >> 56) & 0xFF; - result[1] = (input >> 48) & 0xFF; - result[2] = (input >> 40) & 0xFF; - result[3] = (input >> 32) & 0xFF; - result[4] = (input >> 24) & 0xFF; - result[5] = (input >> 16) & 0xFF; - result[6] = (input >> 8) & 0xFF; - result[7] = (input)&0xFF; - return result; - } - - ///\brief Returns a unique identifier for a track - std::string Track::getIdentifier(){ - std::stringstream result; - if (type == ""){ - result << "metadata_" << trackID; - return result.str(); - }else{ - result << type << "_"; - } - result << codec << "_"; - if (type == "audio"){ - result << channels << "ch_"; - result << rate << "hz"; - }else if (type == "video"){ - result << width << "x" << height << "_"; - result << (double)fpks / 1000 << "fps"; - } - if (lang.size() && lang != "und"){result << "_" << lang;} - return result.str(); - } - - ///\brief Returns a writable identifier for a track, to prevent overwrites on readout - std::string Track::getWritableIdentifier(){ - if (cachedIdent.size()){return cachedIdent;} - std::stringstream result; - result << getIdentifier() << "_" << trackID; - cachedIdent = result.str(); - return cachedIdent; - } - - ///\brief Determines the "packed" size of a track - int Track::getSendLen(bool skipDynamic){ - int result = 124 + init.size() + codec.size() + type.size() + getWritableIdentifier().size(); - if (!skipDynamic){ - result += fragments.size() * PACKED_FRAGMENT_SIZE + 16; - result += keys.size() * PACKED_KEY_SIZE + 11; - result += (keySizes.size() * 4) + 15; - result += parts.size() * 9 + 12; - result += (ivecs.size() * 8) + 12; /*LTS*/ - } - if (lang.size() && lang != "und"){result += 11 + lang.size();} - if (type == "audio"){ - result += 49; - }else if (type == "video"){ - result += 48; - } - if (!skipDynamic && missedFrags){result += 23;} - if (minKeepAway){result += 19;} - return result; - } - - ///\brief Writes a pointer to the specified destination - /// - /// Does a memcpy and increases the destination pointer accordingly - static void writePointer(char *&p, const char *src, unsigned int len){ - memcpy(p, src, len); - p += len; - } - - ///\brief Writes a pointer to the specified destination - /// - /// Does a memcpy and increases the destination pointer accordingly - static void writePointer(char *&p, const std::string &src){ - writePointer(p, src.data(), src.size()); - } - - ///\brief Writes a track to a pointer - void Track::writeTo(char *&p){ - std::deque::iterator firstFrag = fragments.begin(); - if (fragments.size() && (&firstFrag) == 0){return;} - std::string trackIdent = getWritableIdentifier(); - writePointer(p, convertShort(trackIdent.size()), 2); - writePointer(p, trackIdent); - writePointer(p, "\340", 1); // Begin track object - writePointer(p, "\000\011fragments\002", 12); - writePointer(p, convertInt(fragments.size() * PACKED_FRAGMENT_SIZE), 4); - for (; firstFrag != fragments.end(); ++firstFrag){ - writePointer(p, firstFrag->getData(), PACKED_FRAGMENT_SIZE); - } - writePointer(p, "\000\004keys\002", 7); - writePointer(p, convertInt(keys.size() * PACKED_KEY_SIZE), 4); - for (std::deque::iterator it = keys.begin(); it != keys.end(); it++){ - writePointer(p, it->getData(), PACKED_KEY_SIZE); - } - writePointer(p, "\000\010keysizes\002,", 11); - writePointer(p, convertInt(keySizes.size() * 4), 4); - std::string tmp; - tmp.reserve(keySizes.size() * 4); - for (unsigned int i = 0; i < keySizes.size(); i++){ - tmp += (char)(keySizes[i] >> 24); - tmp += (char)(keySizes[i] >> 16); - tmp += (char)(keySizes[i] >> 8); - tmp += (char)(keySizes[i]); - } - writePointer(p, tmp.data(), tmp.size()); - writePointer(p, "\000\005parts\002", 8); - writePointer(p, convertInt(parts.size() * 9), 4); - for (std::deque::iterator it = parts.begin(); it != parts.end(); it++){ - writePointer(p, it->getData(), 9); - } - /*LTS-START*/ - writePointer(p, "\000\005ivecs\002", 8); - writePointer(p, convertInt(ivecs.size() * 8), 4); - for (std::deque::iterator it = ivecs.begin(); it != ivecs.end(); it++){ - writePointer(p, it->getData(), 8); - } - /*LTS-END*/ - writePointer(p, "\000\007trackid\001", 10); - writePointer(p, convertLongLong(trackID), 8); - if (missedFrags){ - writePointer(p, "\000\014missed_frags\001", 15); - writePointer(p, convertLongLong(missedFrags), 8); - } - writePointer(p, "\000\007firstms\001", 10); - writePointer(p, convertLongLong(firstms), 8); - writePointer(p, "\000\006lastms\001", 9); - writePointer(p, convertLongLong(lastms), 8); - writePointer(p, "\000\003bps\001", 6); - writePointer(p, convertLongLong(bps), 8); - writePointer(p, "\000\006maxbps\001", 9); - writePointer(p, convertLongLong(max_bps), 8); - writePointer(p, "\000\004init\002", 7); - writePointer(p, convertInt(init.size()), 4); - writePointer(p, init); - writePointer(p, "\000\005codec\002", 8); - writePointer(p, convertInt(codec.size()), 4); - writePointer(p, codec); - writePointer(p, "\000\004type\002", 7); - writePointer(p, convertInt(type.size()), 4); - writePointer(p, type); - if (lang.size() && lang != "und"){ - writePointer(p, "\000\004lang\002", 7); - writePointer(p, convertInt(lang.size()), 4); - writePointer(p, lang); - } - if (type == "audio"){ - writePointer(p, "\000\004rate\001", 7); - writePointer(p, convertLongLong(rate), 8); - writePointer(p, "\000\004size\001", 7); - writePointer(p, convertLongLong(size), 8); - writePointer(p, "\000\010channels\001", 11); - writePointer(p, convertLongLong(channels), 8); - }else if (type == "video"){ - writePointer(p, "\000\005width\001", 8); - writePointer(p, convertLongLong(width), 8); - writePointer(p, "\000\006height\001", 9); - writePointer(p, convertLongLong(height), 8); - writePointer(p, "\000\004fpks\001", 7); - writePointer(p, convertLongLong(fpks), 8); - } - if (minKeepAway){ - writePointer(p, "\000\010keepaway\001", 11); - writePointer(p, convertLongLong(minKeepAway), 8); - } - writePointer(p, "\000\000\356", 3); // End this track Object - } - - ///\brief Writes a track to a socket - void Track::send(Socket::Connection &conn, bool skipDynamic){ - conn.SendNow(convertShort(getWritableIdentifier().size()), 2); - conn.SendNow(getWritableIdentifier()); - conn.SendNow("\340", 1); // Begin track object - if (!skipDynamic){ - conn.SendNow("\000\011fragments\002", 12); - conn.SendNow(convertInt(fragments.size() * PACKED_FRAGMENT_SIZE), 4); - for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++){ - conn.SendNow(it->getData(), PACKED_FRAGMENT_SIZE); - } - conn.SendNow("\000\004keys\002", 7); - conn.SendNow(convertInt(keys.size() * PACKED_KEY_SIZE), 4); - for (std::deque::iterator it = keys.begin(); it != keys.end(); it++){ - conn.SendNow(it->getData(), PACKED_KEY_SIZE); - } - conn.SendNow("\000\010keysizes\002,", 11); - conn.SendNow(convertInt(keySizes.size() * 4), 4); - std::string tmp; - tmp.reserve(keySizes.size() * 4); - for (unsigned int i = 0; i < keySizes.size(); i++){ - tmp += (char)(keySizes[i] >> 24); - tmp += (char)(keySizes[i] >> 16); - tmp += (char)(keySizes[i] >> 8); - tmp += (char)(keySizes[i]); - } - conn.SendNow(tmp.data(), tmp.size()); - conn.SendNow("\000\005parts\002", 8); - conn.SendNow(convertInt(parts.size() * 9), 4); - for (std::deque::iterator it = parts.begin(); it != parts.end(); it++){ - conn.SendNow(it->getData(), 9); - } - /*LTS-START*/ - conn.SendNow("\000\005ivecs\002", 8); - conn.SendNow(convertInt(ivecs.size() * 8), 4); - for (std::deque::iterator it = ivecs.begin(); it != ivecs.end(); it++){ - conn.SendNow(it->getData(), 8); - } - /*LTS-END*/ - } - conn.SendNow("\000\007trackid\001", 10); - conn.SendNow(convertLongLong(trackID), 8); - if (!skipDynamic && missedFrags){ - conn.SendNow("\000\014missed_frags\001", 15); - conn.SendNow(convertLongLong(missedFrags), 8); - } - conn.SendNow("\000\007firstms\001", 10); - conn.SendNow(convertLongLong(firstms), 8); - conn.SendNow("\000\006lastms\001", 9); - conn.SendNow(convertLongLong(lastms), 8); - conn.SendNow("\000\003bps\001", 6); - conn.SendNow(convertLongLong(bps), 8); - conn.SendNow("\000\006maxbps\001", 9); - conn.SendNow(convertLongLong(max_bps), 8); - conn.SendNow("\000\004init\002", 7); - conn.SendNow(convertInt(init.size()), 4); - conn.SendNow(init); - conn.SendNow("\000\005codec\002", 8); - conn.SendNow(convertInt(codec.size()), 4); - conn.SendNow(codec); - conn.SendNow("\000\004type\002", 7); - conn.SendNow(convertInt(type.size()), 4); - conn.SendNow(type); - if (lang.size() && lang != "und"){ - conn.SendNow("\000\004lang\002", 7); - conn.SendNow(convertInt(lang.size()), 4); - conn.SendNow(lang); - } - if (type == "audio"){ - conn.SendNow("\000\004rate\001", 7); - conn.SendNow(convertLongLong(rate), 8); - conn.SendNow("\000\004size\001", 7); - conn.SendNow(convertLongLong(size), 8); - conn.SendNow("\000\010channels\001", 11); - conn.SendNow(convertLongLong(channels), 8); - }else if (type == "video"){ - conn.SendNow("\000\005width\001", 8); - conn.SendNow(convertLongLong(width), 8); - conn.SendNow("\000\006height\001", 9); - conn.SendNow(convertLongLong(height), 8); - conn.SendNow("\000\004fpks\001", 7); - conn.SendNow(convertLongLong(fpks), 8); - } - if (minKeepAway){ - conn.SendNow("\000\010keepaway\001", 11); - conn.SendNow(convertLongLong(minKeepAway), 8); - } - conn.SendNow("\000\000\356", 3); // End this track Object - } - - ///\brief Determines the "packed" size of a meta object - unsigned int Meta::getSendLen(bool skipDynamic, std::set selectedTracks){ - unsigned int dataLen = - 16 + (vod ? 14 : 0) + (live ? 15 : 0) + (merged ? 17 : 0) + (bufferWindow ? 24 : 0) + 21; - for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++){ - if (!selectedTracks.size() || selectedTracks.count(it->first)){ - dataLen += it->second.getSendLen(skipDynamic); - } - } - if (version){dataLen += 18;} - if (bootMsOffset){dataLen += 21;} - if (sourceURI.size()){dataLen += 13 + sourceURI.size();} - return dataLen + 8; // add 8 bytes header - } - - ///\brief Writes a meta object to a pointer - void Meta::writeTo(char *p){ - int dataLen = getSendLen() - 8; // strip 8 bytes header - writePointer(p, DTSC::Magic_Header, 4); - writePointer(p, convertInt(dataLen), 4); - writePointer(p, "\340\000\006tracks\340", 10); - for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++){ - it->second.writeTo(p); - } - writePointer(p, "\000\000\356", 3); // End tracks object - if (vod){ - writePointer(p, "\000\003vod\001", 6); - writePointer(p, convertLongLong(1), 8); - } - if (live){ - writePointer(p, "\000\004live\001", 7); - writePointer(p, convertLongLong(1), 8); - } - if (merged){ - writePointer(p, "\000\006merged\001", 9); - writePointer(p, convertLongLong(1), 8); - } - if (version){ - writePointer(p, "\000\007version\001", 10); - writePointer(p, convertLongLong(version), 8); - } - if (bootMsOffset){ - writePointer(p, "\000\012bootoffset\001", 13); - writePointer(p, convertLongLong(bootMsOffset), 8); - } - if (sourceURI.size()){ - writePointer(p, "\000\006source\002", 9); - writePointer(p, convertInt(sourceURI.size()), 4); - writePointer(p, sourceURI); - } - if (bufferWindow){ - writePointer(p, "\000\015buffer_window\001", 16); - writePointer(p, convertLongLong(bufferWindow), 8); - } - writePointer(p, "\000\012moreheader\001", 13); - writePointer(p, convertLongLong(moreheader), 8); - writePointer(p, "\000\000\356", 3); // End global object - } - - ///\brief Writes a meta object to a socket - void Meta::send(Socket::Connection &conn, bool skipDynamic, std::set selectedTracks){ - int dataLen = getSendLen(skipDynamic, selectedTracks) - 8; // strip 8 bytes header - conn.SendNow(DTSC::Magic_Header, 4); - conn.SendNow(convertInt(dataLen), 4); - conn.SendNow("\340\000\006tracks\340", 10); - for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++){ - if (!selectedTracks.size() || selectedTracks.count(it->first)){ - it->second.send(conn, skipDynamic); - } - } - conn.SendNow("\000\000\356", 3); // End tracks object - if (vod){ - conn.SendNow("\000\003vod\001", 6); - conn.SendNow(convertLongLong(1), 8); - } - if (live){ - conn.SendNow("\000\004live\001", 7); - conn.SendNow(convertLongLong(1), 8); - } - if (merged){ - conn.SendNow("\000\006merged\001", 9); - conn.SendNow(convertLongLong(1), 8); - } - if (version){ - conn.SendNow("\000\007version\001", 10); - conn.SendNow(convertLongLong(version), 8); - } - if (bootMsOffset){ - conn.SendNow("\000\012bootoffset\001", 13); - conn.SendNow(convertLongLong(bootMsOffset), 8); - } - if (sourceURI.size()){ - conn.SendNow("\000\006source\002", 9); - conn.SendNow(convertInt(sourceURI.size()), 4); - conn.SendNow(sourceURI); - } - if (bufferWindow){ - conn.SendNow("\000\015buffer_window\001", 16); - conn.SendNow(convertLongLong(bufferWindow), 8); - } - conn.SendNow("\000\012moreheader\001", 13); - conn.SendNow(convertLongLong(moreheader), 8); - conn.SendNow("\000\000\356", 3); // End global object - } - - ///\brief Converts a track to a JSON::Value - JSON::Value Track::toJSON(bool skipDynamic){ - JSON::Value result; - std::string tmp; - if (!skipDynamic){ - tmp.reserve(fragments.size() * PACKED_FRAGMENT_SIZE); - for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++){ - tmp.append(it->getData(), PACKED_FRAGMENT_SIZE); - } - result["fragments"] = tmp; - tmp = ""; - tmp.reserve(keys.size() * PACKED_KEY_SIZE); - for (std::deque::iterator it = keys.begin(); it != keys.end(); it++){ - tmp.append(it->getData(), PACKED_KEY_SIZE); - } - result["keys"] = tmp; - tmp = ""; - tmp.reserve(keySizes.size() * 4); - for (unsigned int i = 0; i < keySizes.size(); i++){ - tmp += (char)((keySizes[i] >> 24)); - tmp += (char)((keySizes[i] >> 16)); - tmp += (char)((keySizes[i] >> 8)); - tmp += (char)(keySizes[i]); - } - result["keysizes"] = tmp; - tmp = ""; - tmp.reserve(parts.size() * 9); - for (std::deque::iterator it = parts.begin(); it != parts.end(); it++){ - tmp.append(it->getData(), 9); - } - result["parts"] = tmp; - /*LTS-START*/ - tmp = ""; - tmp.reserve(ivecs.size() * 8); - for (std::deque::iterator it = ivecs.begin(); it != ivecs.end(); it++){ - tmp.append(it->getData(), 8); - } - result["ivecs"] = tmp; - /*LTS-END*/ - } - result["init"] = init; - if (lang.size() && lang != "und"){result["lang"] = lang;} - result["trackid"] = trackID; - result["firstms"] = firstms; - result["lastms"] = lastms; - result["bps"] = bps; - result["maxbps"] = max_bps; - if (missedFrags){result["missed_frags"] = missedFrags;} - result["codec"] = codec; - result["type"] = type; - if (type == "audio"){ - result["rate"] = rate; - result["size"] = size; - result["channels"] = channels; - }else if (type == "video"){ - result["width"] = width; - result["height"] = height; - result["fpks"] = fpks; - } - - if (minKeepAway){result["keepaway"] = minKeepAway;} - - return result; - } - - ///\brief Converts a meta object to a JSON::Value - JSON::Value Meta::toJSON(){ - JSON::Value result; - for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++){ - result["tracks"][it->second.getWritableIdentifier()] = it->second.toJSON(); - } - if (vod){result["vod"] = 1;} - if (live){result["live"] = 1;} - if (merged){result["merged"] = 1;} - if (bufferWindow){result["buffer_window"] = bufferWindow;} - if (version){result["version"] = version;} - if (bootMsOffset){result["bootoffset"] = bootMsOffset;} - if (sourceURI.size()){result["source"] = sourceURI;} - result["moreheader"] = moreheader; - if (inputLocalVars){result["inputlocalvars"] = inputLocalVars;} - return result; - } - - ///\brief Writes metadata to a filename. Wipes existing contents, if any. - bool Meta::toFile(const std::string &fileName){ - std::ofstream oFile(fileName.c_str()); - oFile << toJSON().toNetPacked(); - if (!oFile.good()){return false;} - oFile.close(); - return true; - } - - ///\brief Converts a meta object to a human readable string - ///\param str The stringstream to append to - ///\param indent the amount of indentation needed - ///\param verbosity How verbose the output needs to be - void Meta::toPrettyString(std::ostream &str, int indent, int verbosity){ - for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++){ - it->second.toPrettyString(str, indent, verbosity); - } - if (vod){str << std::string(indent, ' ') << "Video on Demand" << std::endl;} - if (live){str << std::string(indent, ' ') << "Live" << std::endl;} - if (merged){str << std::string(indent, ' ') << "Merged file" << std::endl;} - if (bufferWindow){ - str << std::string(indent, ' ') << "Buffer Window: " << bufferWindow << std::endl; - } - if (sourceURI.size()){ - str << std::string(indent, ' ') << "Source: " << sourceURI << std::endl; - } - if (bootMsOffset){ - str << std::string(indent, ' ') << "Boot MS offset: " << bootMsOffset << std::endl; - } - str << std::string(indent, ' ') << "More Header: " << moreheader << std::endl; - } - - ///\brief Resets a meta object, removes all unimportant meta values - void Meta::reset(){ - for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++){ - it->second.reset(); - } - bootMsOffset = 0; - } - - PartIter::PartIter(Track &Trk, Fragment &frag){ - tRef = &Trk; - pIt = tRef->parts.begin(); - kIt = tRef->keys.begin(); - uint32_t fragNum = frag.getNumber(); - while (kIt->getNumber() < fragNum && kIt != tRef->keys.end()){ - uint32_t kParts = kIt->getParts(); - for (uint32_t pCount = 0; pCount < kParts && pIt != tRef->parts.end(); ++pCount){++pIt;} - ++kIt; - } - if (kIt == tRef->keys.end()){tRef = 0;} - currInKey = 0; - lastKey = fragNum + frag.getLength(); - } - - /// Dereferences into a Value reference. - /// If invalid iterator, returns an empty reference and prints a warning message. - Part &PartIter::operator*() const{ - if (tRef && pIt != tRef->parts.end()){return *pIt;} - static Part error; - WARN_MSG("Dereferenced invalid Part iterator"); - return error; - } - - /// Dereferences into a Value reference. - /// If invalid iterator, returns an empty reference and prints a warning message. - Part *PartIter::operator->() const{return &(operator*());} - - /// True if not done iterating. - PartIter::operator bool() const{return (tRef && pIt != tRef->parts.end());} - - PartIter &PartIter::operator++(){ - if (*this){ - ++pIt; - if (++currInKey >= kIt->getParts()){ - currInKey = 0; - // check if we're done iterating - we assume done if past the last key or arrived past the fragment - if (++kIt == tRef->keys.end() || kIt->getNumber() >= lastKey){tRef = 0;} - } - } - return *this; - } - -}// namespace DTSC diff --git a/lib/encryption.cpp b/lib/encryption.cpp index 29837dc9..8db1746d 100644 --- a/lib/encryption.cpp +++ b/lib/encryption.cpp @@ -1,225 +1,170 @@ -#include "auth.h" #include "bitfields.h" #include "defines.h" -#include "encode.h" #include "encryption.h" -#include "http_parser.h" -#include "nal.h" /*LTS*/ -#include "rijndael.h" -#include -#include -#include -#include -#include -#include +#include "h264.h" namespace Encryption{ - /// helper function for printing binary values - std::string hexString(const char *data, unsigned long dataLen){ - std::stringstream res; - for (int i = 0; i < dataLen; i++){ - res << std::hex << std::setw(2) << std::setfill('0') << (int)data[i]; - if (i % 4 == 3){res << " ";} + AES::AES(){mbedtls_aes_init(&ctx);} + + AES::~AES(){mbedtls_aes_free(&ctx);} + + void AES::setEncryptKey(const char *key){ + 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; size_t dataLen; - thisPack.getString("data", data, dataLen); + src.getString("data", data, dataLen); - if (codec == "H264"){ - unsigned int num = 0; - char expandedKey[224]; - memcpy(expandedKey, key, 16); - char eCount[16]; - char initVec[16]; - memcpy(initVec, iVec, 8); - bool initialize = true; + size_t trackIdx = M.getSourceTrack(newTrack); - int pos = 0; + char *encData = (char *)malloc(dataLen); - std::deque nalSizes = nalu::parseNalSizes(thisPack); - for (std::deque::iterator it = nalSizes.begin(); it != nalSizes.end(); it++){ - int encrypted = (*it - 5) & ~0xF; // Bitmask to a multiple of 16 - int clear = *it - encrypted; - Encryption::AESPartialCrypt(data + pos + clear, encrypted, expandedKey, eCount, initVec, num, initialize); - pos += *it; - } + size_t dataOffset = 0; + + if (M.getType(trackIdx) == "video" && dataLen > 96){ + dataOffset = dataLen - (int((dataLen - 96) / 16) * 16); + memcpy(encData, data, dataOffset); } - if (codec == "AAC"){Encryption::AESFullCrypt(data, dataLen, key, iVec);} - } - /// Converts a hexidecimal string format key to binary string format. - std::string binKey(std::string hexkey){ - char newkey[16]; - memset(newkey, 0, 16); - 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); + if (!encryptBlockCTR(ivec, data + dataOffset, encData + dataOffset, dataLen - dataOffset)){ + FAIL_MSG("Failed to encrypt packet"); + free(encData); + return res; } - return std::string(newkey, 16); - } - /// Helper function for urlescape. - /// Encodes a character as two hex digits. - 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]);} + res.genericFill(src.getTime(), src.getInt("offset"), newTrack, encData, dataLen, 0, src.getFlag("keyframe")); + free(encData); return res; } - void fillVerimatrix(verimatrixData &vmData){ - int hostPos = vmData.url.find("://") + 3; - int portPos = vmData.url.find(":", hostPos); - - std::string hostName = - vmData.url.substr(hostPos, (portPos == std::string::npos ? portPos : portPos - hostPos)); - int port = (portPos == std::string::npos ? 80 : atoi(vmData.url.data() + portPos + 1)); - Socket::Connection veriConn(hostName, port, true); - - 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"); + std::string AES::encryptBlockCTR(uint64_t ivec, const std::string &inp){ + char *resPtr = (char *)malloc(inp.size()); + if (!encryptBlockCTR(ivec, inp.c_str(), resPtr, inp.size())){ + free(resPtr); + return ""; + } + std::string result(resPtr, inp.size()); + free(resPtr); + return result; } - void verimatrixData::read(const char *shmPage){ - int offset = 0; - url = std::string(shmPage + offset); - 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); + bool AES::encryptBlockCTR(uint64_t ivec, const char *src, char *dest, size_t dataLen){ + size_t ncOff = 0; + unsigned char streamBlock[] ={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - 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){ - int offset = 0; - memcpy(shmPage + offset, url.c_str(), url.size() + 1); - offset += url.size() + 1; //+1 for the concluding 0-byte - memcpy(shmPage + offset, name.c_str(), name.size() + 1); - offset += name.size() + 1; //+1 for the concluding 0-byte - std::string tmpKey = hex(key); - memcpy(shmPage + offset, tmpKey.c_str(), tmpKey.size() + 1); - offset += tmpKey.size() + 1; //+1 for the concluding 0-byte - memcpy(shmPage + offset, keyid.c_str(), keyid.size() + 1); - offset += keyid.size() + 1; //+1 for the concluding 0-byte - memcpy(shmPage + offset, laurl.c_str(), laurl.size() + 1); - offset += laurl.size() + 1; //+1 for the concluding 0-byte - memcpy(shmPage + offset, lauurl.c_str(), lauurl.size() + 1); + DTSC::Packet AES::encryptPacketCBC(const DTSC::Meta &M, const DTSC::Packet &src, char *ivec, size_t newTrack){ + DTSC::Packet res; + if (newTrack == INVALID_TRACK_ID){ + FAIL_MSG("No target track given for track encryption!"); + return res; + } + + char *data; + size_t dataLen; + src.getString("data", data, dataLen); + + size_t trackIdx = M.getSourceTrack(newTrack); + + bool encrypt = false; + if (M.getCodec(trackIdx) == "H264"){ + std::deque nalUnits = h264::analysePackets(data, dataLen); + for (std::deque::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 nalUnits = h264::analysePackets(src, dataLen); + for (std::deque::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 diff --git a/lib/encryption.h b/lib/encryption.h index 4fb71906..c50f7935 100644 --- a/lib/encryption.h +++ b/lib/encryption.h @@ -1,35 +1,28 @@ #pragma once #include "dtsc.h" +#include #include namespace Encryption{ - class verimatrixData{ + class AES{ public: - void read(const char *shmPage); - void write(char *shmPage); - std::string url; - std::string name; - std::string key; - std::string keyid; - std::string keyseed; - std::string laurl; - std::string lauurl; + AES(); + ~AES(); + + void setEncryptKey(const char *key); + void setDecryptKey(const char *key); + + DTSC::Packet encryptPacketCTR(const DTSC::Meta &M, const DTSC::Packet &src, uint64_t ivec, size_t newTrack); + std::string encryptBlockCTR(uint64_t ivec, const std::string &inp); + 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 diff --git a/lib/flv_tag.cpp b/lib/flv_tag.cpp index b7fc6c9b..aac36565 100644 --- a/lib/flv_tag.cpp +++ b/lib/flv_tag.cpp @@ -4,6 +4,7 @@ #include "adts.h" #include "defines.h" #include "flv_tag.h" +#include "mp4_generic.h" #include "rtmpchunks.h" #include "timing.h" #include "util.h" @@ -323,17 +324,23 @@ FLV::Tag &FLV::Tag::operator=(const FLV::Tag &O){ return *this; }// 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; 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; size_t tmpLen = 0; packData.getString("data", tmpData, tmpLen); len = tmpLen + 16; - if (track.codec == "H264"){len += 4;} + if (codec == "H264"){len += 4;} if (!checkBufferSize()){return false;} - if (track.codec == "H264"){ + if (codec == "H264"){ memcpy(data + 16, tmpData, len - 20); data[12] = 1; offset(packData.getInt("offset")); @@ -341,13 +348,13 @@ bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, DTSC::Track &track){ memcpy(data + 12, tmpData, len - 16); } data[11] = 0; - if (track.codec == "H264"){data[11] |= 7;} - if (track.codec == "ScreenVideo2"){data[11] |= 6;} - if (track.codec == "VP6Alpha"){data[11] |= 5;} - if (track.codec == "VP6"){data[11] |= 4;} - if (track.codec == "ScreenVideo1"){data[11] |= 3;} - if (track.codec == "H263"){data[11] |= 2;} - if (track.codec == "JPEG"){data[11] |= 1;} + if (codec == "H264"){data[11] |= 7;} + if (codec == "ScreenVideo2"){data[11] |= 6;} + if (codec == "VP6Alpha"){data[11] |= 5;} + if (codec == "VP6"){data[11] |= 4;} + if (codec == "ScreenVideo1"){data[11] |= 3;} + if (codec == "H263"){data[11] |= 2;} + if (codec == "JPEG"){data[11] |= 1;} if (packData.getFlag("keyframe")){ data[11] |= 0x10; }else{ @@ -355,32 +362,32 @@ bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, DTSC::Track &track){ } if (packData.getFlag("disposableframe")){data[11] |= 0x30;} } - if (track.type == "audio"){ + if (type == "audio"){ char *tmpData = 0; size_t tmpLen = 0; packData.getString("data", tmpData, tmpLen); len = tmpLen + 16; - if (track.codec == "AAC"){len++;} + if (codec == "AAC"){len++;} if (!checkBufferSize()){return false;} - if (track.codec == "AAC"){ + if (codec == "AAC"){ memcpy(data + 13, tmpData, len - 17); data[12] = 1; // raw AAC data, not sequence header }else{ memcpy(data + 12, tmpData, len - 16); } - unsigned int datarate = track.rate; + unsigned int datarate = M.getRate(idx); data[11] = 0; - if (track.codec == "AAC"){data[11] |= 0xA0;} - if (track.codec == "MP3"){ + if (codec == "AAC"){data[11] |= 0xA0;} + if (codec == "MP3"){ if (datarate == 8000){ data[11] |= 0xE0; }else{ data[11] |= 0x20; } } - if (track.codec == "ADPCM"){data[11] |= 0x10;} - if (track.codec == "PCM"){data[11] |= 0x30;} - if (track.codec == "Nellymoser"){ + if (codec == "ADPCM"){data[11] |= 0x10;} + if (codec == "PCM"){data[11] |= 0x30;} + if (codec == "Nellymoser"){ if (datarate == 8000){ data[11] |= 0x50; }else if (datarate == 16000){ @@ -389,9 +396,9 @@ bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, DTSC::Track &track){ data[11] |= 0x60; } } - if (track.codec == "ALAW"){data[11] |= 0x70;} - if (track.codec == "ULAW"){data[11] |= 0x80;} - if (track.codec == "Speex"){data[11] |= 0xB0;} + if (codec == "ALAW"){data[11] |= 0x70;} + if (codec == "ULAW"){data[11] |= 0x80;} + if (codec == "Speex"){data[11] |= 0xB0;} if (datarate >= 44100){ data[11] |= 0x0C; }else if (datarate >= 22050){ @@ -399,14 +406,14 @@ bool FLV::Tag::DTSCLoader(DTSC::Packet &packData, DTSC::Track &track){ }else if (datarate >= 11025){ data[11] |= 0x04; } - if (track.size != 8){data[11] |= 0x02;} - if (track.channels > 1){data[11] |= 0x01;} + if (M.getSize(idx) != 8){data[11] |= 0x02;} + if (M.getChannels(idx) > 1){data[11] |= 0x01;} } if (!len){return false;} setLen(); - if (track.type == "video"){data[0] = 0x09;} - if (track.type == "audio"){data[0] = 0x08;} - if (track.type == "meta"){data[0] = 0x12;} + if (type == "video"){data[0] = 0x09;} + if (type == "audio"){data[0] = 0x08;} + if (type == "meta"){data[0] = 0x12;} data[1] = ((len - 15) >> 16) & 0xFF; data[2] = ((len - 15) >> 8) & 0xFF; data[3] = (len - 15) & 0xFF; @@ -431,13 +438,14 @@ void FLV::Tag::setLen(){ } /// 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. len = 0; - if (video.codec == "?"){video.codec = "H264";} - if (video.codec == "H264"){len = video.init.size() + 20;} + if (meta.getCodec(vTrack) == "?"){meta.setCodec(vTrack, "H264");} + std::string initData = meta.getInit(vTrack); + if (meta.getCodec(vTrack) == "H264"){len = initData.size() + 20;} 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[13] = 0; data[14] = 0; @@ -456,18 +464,19 @@ bool FLV::Tag::DTSCVideoInit(DTSC::Track &video){ } /// 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; // Unknown? Assume AAC. - if (audio.codec == "?"){audio.codec = "AAC";} - if (audio.codec == "AAC"){len = audio.init.size() + 17;} + if (meta.getCodec(aTrack) == "?"){meta.setCodec(aTrack, "AAC");} + std::string initData = meta.getInit(aTrack); + if (meta.getCodec(aTrack) == "AAC"){len = initData.size() + 17;} 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[11] = 0; - if (audio.codec == "AAC"){data[11] += 0xA0;} - if (audio.codec == "MP3"){data[11] += 0x20;} - unsigned int datarate = audio.rate; + if (meta.getCodec(aTrack) == "AAC"){data[11] += 0xA0;} + if (meta.getCodec(aTrack) == "MP3"){data[11] += 0x20;} + unsigned int datarate = meta.getRate(aTrack); if (datarate >= 44100){ data[11] += 0x0C; }else if (datarate >= 22050){ @@ -475,8 +484,8 @@ bool FLV::Tag::DTSCAudioInit(DTSC::Track &audio){ }else if (datarate >= 11025){ data[11] += 0x04; } - if (audio.size != 8){data[11] += 0x02;} - if (audio.channels > 1){data[11] += 0x01;} + if (meta.getSize(aTrack) != 8){data[11] += 0x02;} + if (meta.getChannels(aTrack) > 1){data[11] += 0x01;} setLen(); data[0] = 0x08; data[1] = ((len - 15) >> 16) & 0xFF; @@ -489,86 +498,87 @@ bool FLV::Tag::DTSCAudioInit(DTSC::Track &audio){ return true; } -bool FLV::Tag::DTSCMetaInit(DTSC::Meta &M, std::set &selTracks){ +bool FLV::Tag::DTSCMetaInit(const DTSC::Meta &M, std::set &selTracks){ AMF::Object amfdata("root", AMF::AMF0_DDV_CONTAINER); amfdata.addContent(AMF::Object("", "onMetaData")); amfdata.addContent(AMF::Object("", AMF::AMF0_ECMA_ARRAY)); AMF::Object trinfo = AMF::Object("trackinfo", AMF::AMF0_STRICT_ARRAY); int i = 0; - unsigned long long mediaLen = 0; + uint64_t mediaLen = 0; for (std::set::iterator it = selTracks.begin(); it != selTracks.end(); it++){ - if (M.tracks[*it].lastms - M.tracks[*it].firstms > mediaLen){ - mediaLen = M.tracks[*it].lastms - M.tracks[*it].firstms; + if (M.getLastms(*it) - M.getFirstms(*it) > mediaLen){ + 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.getContentP(i)->addContent(AMF::Object( - "length", ((double)M.tracks[*it].lastms / 1000) * ((double)M.tracks[*it].fpks / 1000.0), AMF::AMF0_NUMBER)); - trinfo.getContentP(i)->addContent( - AMF::Object("timescale", ((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(AMF::Object("timescale", ((double)M.getFpks(*it) / 1000), AMF::AMF0_NUMBER)); trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); 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)); - 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)); - 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)); - 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)); - 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)); - 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)); - 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)); - 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("height", M.tracks[*it].height, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("width", M.getWidth(*it), AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("height", M.getHeight(*it), AMF::AMF0_NUMBER)); 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( - 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; } - if (M.tracks[*it].type == "audio"){ + if (M.getType(*it) == "audio"){ trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT)); - trinfo.getContentP(i)->addContent(AMF::Object( - "length", ((double)M.tracks[*it].lastms) * ((double)M.tracks[*it].rate), AMF::AMF0_NUMBER)); - trinfo.getContentP(i)->addContent(AMF::Object("timescale", M.tracks[*it].rate, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->addContent( + AMF::Object("length", (double)(M.getLastms(*it) * M.getRate(*it)), 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)); amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL)); amfdata.getContentP(1)->addContent(AMF::Object("audiodelay", 0.0, AMF::AMF0_NUMBER)); - if (M.tracks[*it].codec == "AAC"){ - amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", (std::string) "mp4a")); - trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "mp4a")); + std::string codec = M.getCodec(*it); + if (codec == "AAC"){ + amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", "mp4a")); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", "mp4a")); } - if (M.tracks[*it].codec == "MP3"){ - amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", (std::string) "mp3")); - trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string) "mp3")); + if (codec == "MP3"){ + amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", "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("audiosamplerate", M.tracks[*it].rate, AMF::AMF0_NUMBER)); - amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", M.tracks[*it].size, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("audiochannels", M.getChannels(*it), AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("audiosamplerate", M.getRate(*it), AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", M.getSize(*it), AMF::AMF0_NUMBER)); 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; } } - if (M.vod){ + if (M.getVod()){ amfdata.getContentP(1)->addContent(AMF::Object("duration", mediaLen / 1000, AMF::AMF0_NUMBER)); } amfdata.getContentP(1)->addContent(trinfo); @@ -742,8 +752,8 @@ bool FLV::Tag::FileLoader(FILE *f){ }else{ // if a tag header, calculate length and read tag body len = data[3] + 15; - len += (data[2] << 8); - len += (data[1] << 16); + len += (((unsigned int)data[2]) << 8); + len += (((unsigned int)data[1]) << 16); if (!checkBufferSize()){return false;} if (data[0] > 0x12){ data[0] += 32; @@ -781,7 +791,7 @@ unsigned int FLV::Tag::getTrackID(){ case 0x08: return 2; // audio track case 0x09: return 1; // video 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; } -void FLV::Tag::toMeta(DTSC::Meta &metadata, AMF::Object &amf_storage, unsigned int reTrack){ - if (!reTrack){ - switch (data[0]){ - case 0x09: reTrack = 1; break; // video - case 0x08: reTrack = 2; break; // audio - case 0x12: reTrack = 3; break; // meta - } +void FLV::Tag::toMeta(DTSC::Meta &meta, AMF::Object &amf_storage){ + size_t reTrack = INVALID_TRACK_ID; + toMeta(meta, amf_storage, reTrack); +} +void FLV::Tag::toMeta(DTSC::Meta &meta, AMF::Object &amf_storage, size_t &reTrack){ + 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){ @@ -828,45 +841,44 @@ void FLV::Tag::toMeta(DTSC::Meta &metadata, AMF::Object &amf_storage, unsigned i if (tmp){amf_storage = *tmp;} 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]; - metadata.tracks[reTrack].trackID = reTrack; - metadata.tracks[reTrack].type = "audio"; - metadata.tracks[reTrack].codec = getAudioCodec(); + meta.setType(reTrack, "audio"); + meta.setCodec(reTrack, getAudioCodec()); switch (audiodata & 0x0C){ - case 0x0: metadata.tracks[reTrack].rate = 5512; break; - case 0x4: metadata.tracks[reTrack].rate = 11025; break; - case 0x8: metadata.tracks[reTrack].rate = 22050; break; - case 0xC: metadata.tracks[reTrack].rate = 44100; break; + case 0x0: meta.setRate(reTrack, 5512); break; + case 0x4: meta.setRate(reTrack, 11025); break; + case 0x8: meta.setRate(reTrack, 22050); break; + case 0xC: meta.setRate(reTrack, 44100); break; } if (amf_storage.getContentP("audiosamplerate")){ - metadata.tracks[reTrack].rate = (long long int)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.setRate(reTrack, amf_storage.getContentP("audiosamplerate")->NumValue()); } + meta.setSize(reTrack, audiodata & 0x02 ? 16 : 8); if (amf_storage.getContentP("audiosamplesize")){ - metadata.tracks[reTrack].size = (long long int)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.setSize(reTrack, amf_storage.getContentP("audiosamplesize")->NumValue()); } + meta.setChannels(reTrack, audiodata & 0x01 ? 2 : 1); if (amf_storage.getContentP("stereo")){ - if (amf_storage.getContentP("stereo")->NumValue() == 1){ - metadata.tracks[reTrack].channels = 2; - }else{ - metadata.tracks[reTrack].channels = 1; - } + meta.setChannels(reTrack, amf_storage.getContentP("stereo")->NumValue() == 1 ? 2 : 1); } if (needsInitData() && isInitData()){ 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{ - 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"){ 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]; - metadata.tracks[reTrack].codec = getVideoCodec(); - metadata.tracks[reTrack].type = "video"; - metadata.tracks[reTrack].trackID = reTrack; + meta.setCodec(reTrack, getVideoCodec()); + meta.setType(reTrack, "video"); 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")){ - 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()){ - metadata.tracks[reTrack].fpks = - (long long int)(amf_storage.getContentP("videoframerate")->NumValue() * 1000.0); + meta.setFpks(reTrack, amf_storage.getContentP("videoframerate")->NumValue() * 1000.0); }else{ - metadata.tracks[reTrack].fpks = - atoi(amf_storage.getContentP("videoframerate")->StrValue().c_str()) * 1000.0; + meta.setFpks(reTrack, atoi(amf_storage.getContentP("videoframerate")->StrValue().c_str()) * 1000.0); } } if (needsInitData() && isInitData()){ if ((videodata & 0x0F) == 7){ 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{ 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 /// we do need correct data... - if (!metadata.tracks[reTrack].width || !metadata.tracks[reTrack].height || - !metadata.tracks[reTrack].fpks){ + if (!meta.getWidth(reTrack) || !meta.getHeight(reTrack) || !meta.getFpks(reTrack)){ + std::string init = meta.getInit(reTrack); h264::sequenceParameterSet sps; - sps.fromDTSCInit(metadata.tracks[reTrack].init); + sps.fromDTSCInit(init); h264::SPSMeta spsChar = sps.getCharacteristics(); - metadata.tracks[reTrack].width = spsChar.width; - metadata.tracks[reTrack].height = spsChar.height; - metadata.tracks[reTrack].fpks = spsChar.fps * 1000; + meta.setWidth(reTrack, spsChar.width); + meta.setHeight(reTrack, spsChar.height); + meta.setFpks(reTrack, spsChar.fps * 1000); } } } diff --git a/lib/flv_tag.h b/lib/flv_tag.h index d2415ee2..dff8eda3 100644 --- a/lib/flv_tag.h +++ b/lib/flv_tag.h @@ -49,11 +49,12 @@ namespace FLV{ ~Tag(); ///< Generic destructor. // loader functions bool ChunkLoader(const RTMPStream::Chunk &O); - bool DTSCLoader(DTSC::Packet &packData, DTSC::Track &track); - bool DTSCVideoInit(DTSC::Track &video); - bool DTSCAudioInit(DTSC::Track &audio); - bool DTSCMetaInit(DTSC::Meta &M, std::set &selTracks); - void toMeta(DTSC::Meta &metadata, AMF::Object &amf_storage, unsigned int reTrack = 0); + bool DTSCLoader(DTSC::Packet &packData, const DTSC::Meta &M, size_t idx); + bool DTSCVideoInit(DTSC::Meta &meta, uint32_t vTrack); + bool DTSCAudioInit(DTSC::Meta &meta, uint32_t aTrack); + bool DTSCMetaInit(const DTSC::Meta &M, std::set &selTracks); + 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 FileLoader(FILE *f); unsigned int getTrackID(); diff --git a/lib/h264.cpp b/lib/h264.cpp index cee9353a..464c2be4 100644 --- a/lib/h264.cpp +++ b/lib/h264.cpp @@ -1039,7 +1039,6 @@ namespace h264{ if (videoSignalTypePresentFlag){ videoFormat = bs.get(3); videoFullRangeFlag = bs.get(1); - ; colourDescriptionPresentFlag = bs.get(1); if (colourDescriptionPresentFlag){ colourPrimaries = bs.get(8); diff --git a/lib/h264.h b/lib/h264.h index daf68dcc..e05e4a36 100644 --- a/lib/h264.h +++ b/lib/h264.h @@ -44,7 +44,6 @@ namespace h264{ uint32_t chroma_format_idc; ///< the value of chroma_format_idc std::string MyData; ///< The h264 nal unit data }; - // NAL class /// Special instance of NAL class for analyzing SPS nal units class SPS : public NAL{ @@ -96,7 +95,6 @@ namespace h264{ virtual void setSPSNumber(size_t newNumber){} virtual void setPPSNumber(size_t newNumber){} - protected: std::string payload; }; diff --git a/lib/h265.cpp b/lib/h265.cpp index ef505e74..cbf90cce 100644 --- a/lib/h265.cpp +++ b/lib/h265.cpp @@ -509,8 +509,7 @@ namespace h265{ profileTierLevel(bs, maxSubLayersMinus1, res); bs.getUExpGolomb(); uint64_t chromaFormatIdc = bs.getUExpGolomb(); - bool separateColorPlane = false; - if (chromaFormatIdc == 3){separateColorPlane = bs.get(1);} + if (chromaFormatIdc == 3){bs.skip(1);} res.width = bs.getUExpGolomb(); res.height = bs.getUExpGolomb(); bool conformanceWindow = bs.get(1); diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index dd23f06b..6fb277ce 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -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, Socket::Connection &conn, bool bufferAllChunks){ 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(); protocol = prot; if (sendingChunks){ diff --git a/lib/json.h b/lib/json.h index a8c67275..e4c4839a 100644 --- a/lib/json.h +++ b/lib/json.h @@ -1,5 +1,4 @@ /// \file json.h Holds all JSON-related headers. - #pragma once #include "socket.h" #include diff --git a/lib/mp4.h b/lib/mp4.h index 8d2ce17e..4ed9dd59 100644 --- a/lib/mp4.h +++ b/lib/mp4.h @@ -80,6 +80,7 @@ namespace MP4{ class fullBox : public Box{ public: fullBox(); + fullBox(const Box &rs) : Box(rs){} void setVersion(char newVersion); char getVersion() const; void setFlags(uint32_t newFlags); diff --git a/lib/mp4_generic.cpp b/lib/mp4_generic.cpp index e8ab2b30..b4580949 100644 --- a/lib/mp4_generic.cpp +++ b/lib/mp4_generic.cpp @@ -4,9 +4,10 @@ #include "mp4_generic.h" namespace MP4{ - MFHD::MFHD(){ + MFHD::MFHD(uint32_t sequenceNumber){ memcpy(data + 4, "mfhd", 4); setInt32(0, 0); + setSequenceNumber(sequenceNumber); } 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. - VMHD::VMHD(){ + VMHD::VMHD(uint32_t version, uint32_t flags){ + setVersion(version); + setFlags(flags); memcpy(data + 4, "vmhd", 4); setGraphicsMode(0); setOpColor(0, 0); @@ -1563,7 +1566,7 @@ namespace MP4{ uint32_t offset = 8; // start of boxes for (i = 0; i < getEntryCount() && i < index; i++){offset += getBoxLen(offset);} if (index + 1 > getEntryCount()){ - int amount = index + 1 - getEntryCount(); + int amount = index - getEntryCount(); if (!reserve(payloadOffset + offset, 0, amount * 8)){return;} for (int j = 0; j < amount; ++j){ memcpy(data + payloadOffset + offset + j * 8, "\000\000\000\010erro", 8); @@ -1922,14 +1925,14 @@ namespace MP4{ setHeight(height); } - TKHD::TKHD(DTSC::Track &track, bool fragmented){ + TKHD::TKHD(const DTSC::Meta &M, size_t idx){ initialize(); - setTrackID(track.trackID); + setTrackID(idx + 1); setDuration(-1); - if (!fragmented){setDuration(track.lastms - track.firstms);} - if (track.type == "video"){ - setWidth(track.width); - setHeight(track.height); + if (M.getVod()){setDuration(M.getLastms(idx) - M.getFirstms(idx));} + if (M.getType(idx) == "video"){ + setWidth(M.getWidth(idx)); + setHeight(M.getHeight(idx)); } } @@ -2136,7 +2139,7 @@ namespace MP4{ return r.str(); } - MDHD::MDHD(uint64_t duration){ + MDHD::MDHD(uint64_t duration, const std::string &language){ memcpy(data + 4, "mdhd", 4); // reserve an entire version 0 box if (!reserve(0, 9, 32)){ @@ -2146,6 +2149,7 @@ namespace MP4{ setTimeScale(1000); setDuration(duration); + setLanguage(language); } void MDHD::setCreationTime(uint64_t newCreationTime){ @@ -2643,22 +2647,23 @@ namespace MP4{ VisualSampleEntry::VisualSampleEntry(){initialize();} - VisualSampleEntry::VisualSampleEntry(DTSC::Track &track){ + VisualSampleEntry::VisualSampleEntry(const DTSC::Meta &M, size_t idx){ + std::string tCodec = M.getCodec(idx); initialize(); setDataReferenceIndex(1); - setWidth(track.width); - setHeight(track.height); - if (track.codec == "H264"){ + setWidth(M.getWidth(idx)); + setHeight(M.getHeight(idx)); + if (tCodec == "H264"){ setCodec("avc1"); MP4::AVCC avccBox; - avccBox.setPayload(track.init); + avccBox.setPayload(M.getInit(idx)); setCLAP(avccBox); } /*LTS-START*/ - if (track.codec == "HEVC"){ + if (tCodec == "HEVC"){ setCodec("hev1"); MP4::HVCC hvccBox; - hvccBox.setPayload(track.init); + hvccBox.setPayload(M.getInit(idx)); setCLAP(hvccBox); } /*LTS-END*/ @@ -2678,6 +2683,8 @@ namespace MP4{ 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);} uint16_t VisualSampleEntry::getWidth(){return getInt16(24);} @@ -2815,19 +2822,20 @@ namespace MP4{ AudioSampleEntry::AudioSampleEntry(){initialize();} - AudioSampleEntry::AudioSampleEntry(DTSC::Track &track){ + AudioSampleEntry::AudioSampleEntry(const DTSC::Meta &M, size_t idx){ + std::string tCodec = M.getCodec(idx); initialize(); - if (track.codec == "AAC" || track.codec == "MP3"){setCodec("mp4a");} - if (track.codec == "AC3"){setCodec("ac-3");} + if (tCodec == "AAC" || tCodec == "MP3"){setCodec("mp4a");} + if (tCodec == "AC3"){setCodec("ac-3");} setDataReferenceIndex(1); - setSampleRate(track.rate); - setChannelCount(track.channels); - setSampleSize(track.size); - if (track.codec == "AC3"){ - MP4::DAC3 dac3Box(track.rate, track.channels); + setSampleRate(M.getRate(idx)); + setChannelCount(M.getChannels(idx)); + setSampleSize(M.getSize(idx)); + if (tCodec == "AC3"){ + MP4::DAC3 dac3Box(M.getRate(idx), M.getChannels(idx)); setCodecBox(dac3Box); }else{// other codecs use the ESDS box - MP4::ESDS esdsBox(track.init); + MP4::ESDS esdsBox(M.getInit(idx)); setCodecBox(esdsBox); } } @@ -2938,14 +2946,14 @@ namespace MP4{ return r.str(); } - TextSampleEntry::TextSampleEntry(DTSC::Track &track){ + TextSampleEntry::TextSampleEntry(const DTSC::Meta &M, size_t idx){ initialize(); - if (track.codec == "subtitle"){ + if (M.getCodec(idx) == "subtitle"){ setCodec("tx3g"); }else{ // 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"); } - 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);} diff --git a/lib/mp4_generic.h b/lib/mp4_generic.h index c241364e..8eb66000 100644 --- a/lib/mp4_generic.h +++ b/lib/mp4_generic.h @@ -8,7 +8,7 @@ namespace h265{ namespace MP4{ class MFHD : public Box{ public: - MFHD(); + MFHD(uint32_t sequenceNumber = 0); void setSequenceNumber(uint32_t newSequenceNumber); uint32_t getSequenceNumber(); std::string toPrettyString(uint32_t indent = 0); @@ -357,7 +357,7 @@ namespace MP4{ class VMHD : public fullBox{ public: - VMHD(); + VMHD(uint32_t version = 0, uint32_t flags = 0); void setGraphicsMode(uint16_t newGraphicsMode); uint16_t getGraphicsMode(); uint32_t getOpColorCount(); @@ -492,8 +492,9 @@ namespace MP4{ class TKHD : public fullBox{ 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(DTSC::Track &track, bool fragmented); + TKHD(const DTSC::Meta &M, size_t idx); void setCreationTime(uint64_t newCreationTime); uint64_t getCreationTime(); @@ -527,7 +528,7 @@ namespace MP4{ class MDHD : public fullBox{ public: - MDHD(uint64_t duration = 0); + MDHD(uint64_t duration = 0, const std::string &language = ""); void setCreationTime(uint64_t newCreationTime); uint64_t getCreationTime(); void setModificationTime(uint64_t newModificationTime); @@ -593,6 +594,7 @@ namespace MP4{ class STCO : public fullBox{ public: + STCO(const Box &rs) : fullBox(rs){} STCO(char v = 1, uint32_t f = 0); void setEntryCount(uint32_t newEntryCount); uint32_t getEntryCount(); @@ -604,6 +606,7 @@ namespace MP4{ class CO64 : public fullBox{ public: CO64(char v = 1, uint32_t f = 0); + CO64(const Box &rs) : fullBox(rs){} void setEntryCount(uint32_t newEntryCount); uint32_t getEntryCount(); void setChunkOffset(uint64_t newChunkOffset, uint32_t no); @@ -667,9 +670,10 @@ namespace MP4{ ///\todo set default values public: VisualSampleEntry(); - VisualSampleEntry(DTSC::Track &track); + VisualSampleEntry(const DTSC::Meta &M, size_t idx); void initialize(); void setCodec(const char *newCodec); + std::string getCodec(); void setWidth(uint16_t newWidth); uint16_t getWidth(); void setHeight(uint16_t newHeight); @@ -700,7 +704,7 @@ namespace MP4{ public: ///\todo set default values AudioSampleEntry(); - AudioSampleEntry(DTSC::Track &track); + AudioSampleEntry(const DTSC::Meta &M, size_t idx); void initialize(); void setCodec(const char *newCodec); void setChannelCount(uint16_t newChannelCount); @@ -761,7 +765,7 @@ namespace MP4{ class TextSampleEntry : public SampleEntry{ public: TextSampleEntry(); - TextSampleEntry(DTSC::Track &track); + TextSampleEntry(const DTSC::Meta &m, size_t idx); void initialize(); void setHzJustification(int8_t n); void setVtJustification(int8_t n); diff --git a/lib/mpeg.cpp b/lib/mpeg.cpp index 3219b7fa..a80c93ad 100644 --- a/lib/mpeg.cpp +++ b/lib/mpeg.cpp @@ -13,7 +13,7 @@ namespace Mpeg{ // samplerate is encoded in bits 0x0C of header[2]; res.sampleRate = sampleRates[mpegVersion][((hdr[2] >> 2) & 0x03)] * 1000; res.channels = 2 - (hdr[3] >> 7); - res.layer = 4 - (hdr[1] >> 1) & 0x03; + res.layer = 4 - ((hdr[1] >> 1) & 0x03); return res; } diff --git a/lib/rtp.cpp b/lib/rtp.cpp index ee57f621..b0d452ba 100644 --- a/lib/rtp.cpp +++ b/lib/rtp.cpp @@ -25,36 +25,36 @@ namespace RTP{ 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);} - 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), - const char *payload, unsigned int payloadlen, unsigned int channel, bool lastOfAccesUnit){ + void Packet::sendH264(void *socket, void callBack(void *, const char *, size_t, uint8_t), + const char *payload, uint32_t payloadlen, uint32_t channel, bool lastOfAccesUnit){ if ((payload[0] & 0x1F) == 12){return;} /// \todo This function probably belongs in DMS somewhere. 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){ bool isKeyframe = ((payload[0] & 0x01) == 0) ? true : false; @@ -133,7 +133,7 @@ namespace RTP{ // 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){ /// \todo This function probably belongs in DMS somewhere. 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){ /// \todo This function probably belongs in DMS somewhere. if (payloadlen + getHsize() + 4 <= maxDataLen){ @@ -223,8 +223,8 @@ namespace RTP{ } } - void Packet::sendData(void *socket, void callBack(void *, char *, unsigned int, unsigned int), - const char *payload, unsigned int payloadlen, unsigned int channel, std::string codec){ + void Packet::sendData(void *socket, void callBack(void *, const char *, size_t, uint8_t), const char *payload, + unsigned int payloadlen, unsigned int channel, std::string codec){ if (codec == "H264"){ unsigned long sent = 0; while (sent < payloadlen){ @@ -254,18 +254,18 @@ namespace RTP{ } /// \todo This function probably belongs in DMS somewhere. data[1] |= 0x80; // setting the RTP marker bit to 1 - long offsetLen = 0; + size_t offsetLen = 0; if (codec == "AAC"){ - *((long *)(data + getHsize())) = htonl(((payloadlen << 3) & 0x0010fff8) | 0x00100000); + Bit::htobl(data + getHsize(), ((payloadlen << 3) & 0x0010fff8) | 0x00100000); offsetLen = 4; }else if (codec == "MP3" || codec == "MP2"){ // 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?");} offsetLen = 4; }else if (codec == "AC3"){ - *((short *)(data + getHsize())) = htons(0x0001); // this is 6 bits MBZ, 2 bits FT = 0 = full - // frames and 8 bits saying we send 1 frame + Bit::htobs(data + getHsize(), + 1); // this is 6 bits MBZ, 2 bits FT = 0 = full frames and 8 bits saying we send 1 frame offsetLen = 2; } if (maxDataLen < getHsize() + offsetLen + payloadlen){ @@ -289,8 +289,7 @@ namespace RTP{ increaseSequence(); } - void Packet::sendRTCP_SR(long long &connectedAt, void *socket, unsigned int tid, DTSC::Meta &metadata, - void callBack(void *, char *, unsigned int, unsigned int)){ + void Packet::sendRTCP_SR(void *socket, void callBack(void *, const char *, size_t, uint8_t)){ char *rtcpData = (char *)malloc(32); if (!rtcpData){ FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up."); @@ -311,8 +310,7 @@ namespace RTP{ free(rtcpData); } - void Packet::sendRTCP_RR(long long &connectedAt, SDP::Track &sTrk, unsigned int tid, DTSC::Meta &metadata, - void callBack(void *, char *, unsigned int, unsigned int)){ + void Packet::sendRTCP_RR(SDP::Track &sTrk, void callBack(void *, const char *, size_t, uint8_t)){ char *rtcpData = (char *)malloc(32); if (!rtcpData){ 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 + 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 - callBack(&(sTrk.rtcp), (char *)rtcpData, 32, 0); + callBack(&(sTrk.rtcp), rtcpData, 32, 0); sTrk.sorter.lostCurrent = 0; sTrk.sorter.packCurrent = 0; free(rtcpData); @@ -344,8 +342,7 @@ namespace RTP{ sentPackets = 0; } - Packet::Packet(unsigned int payloadType, unsigned int sequence, unsigned int timestamp, - unsigned int ssrc, unsigned int csrcCount){ + Packet::Packet(uint32_t payloadType, uint32_t sequence, uint64_t timestamp, uint32_t ssrc, uint32_t csrcCount){ managed = true; data = new char[12 + 4 * csrcCount + 2 + MAX_SEND]; // headerSize, 2 for FU-A, MAX_SEND for maximum sent size if (data){ @@ -409,7 +406,7 @@ namespace RTP{ Packet::~Packet(){ if (managed){delete[] data;} } - Packet::Packet(const char *dat, unsigned int len){ + Packet::Packet(const char *dat, uint64_t len){ managed = false; maxDataLen = len; sentBytes = 0; @@ -496,7 +493,7 @@ namespace RTP{ while (packBuffer.count(rtpSeq)){ outPacket(packTrack, packBuffer[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; ++packTotal; ++packCurrent; @@ -506,7 +503,7 @@ namespace RTP{ while (packBuffer.count(rtpSeq)){ outPacket(packTrack, packBuffer[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; ++packTotal; ++packCurrent; @@ -542,7 +539,7 @@ namespace RTP{ cbPack = 0; cbInit = 0; multiplier = 1.0; - trackId = 0; + trackId = INVALID_TRACK_ID; firstTime = 0; packCount = 0; lastSeq = 0; @@ -572,10 +569,12 @@ namespace RTP{ } } - void toDTSC::setProperties(const DTSC::Track &Trk){ - double m = (double)Trk.rate / 1000.0; - if (Trk.type == "video" || Trk.codec == "MP2" || Trk.codec == "MP3"){m = 90.0;} - setProperties(Trk.trackID, Trk.codec, Trk.type, Trk.init, m); + void toDTSC::setProperties(const DTSC::Meta &M, size_t tid){ + double m = (double)M.getRate(tid) / 1000.0; + if (M.getType(tid) == "video" || M.getCodec(tid) == "MP2" || M.getCodec(tid) == "MP3"){ + m = 90.0; + } + setProperties(tid, M.getCodec(tid), M.getType(tid), M.getInit(tid), m); } void toDTSC::setCallbacks(void (*cbP)(const DTSC::Packet &pkt), @@ -610,11 +609,12 @@ namespace RTP{ } prevTime = pkt.getTimeStamp(); 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(); bool missed = lastSeq != (pkt.getSequence() - 1); 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, // except for the trivial codecs. if (codec == "H264"){ @@ -749,7 +749,7 @@ namespace RTP{ } 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) if (!len){return;} @@ -787,11 +787,13 @@ namespace RTP{ offset = (frameNo - packCount) * (1000.0 / fps); //... and the timestamp is the packet counter times the frame rate in ms. newTs = packCount * (1000.0 / fps); - VERYHIGH_MSG("Packing time %llu = %sframe %llu (%.2f FPS). Expected %llu -> +%llu/%lu", ts, - isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset); + VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (%.2f FPS). Expected %" PRIu64 + " -> +%" PRIu64 "/%" PRIu32, + ts, (isKey ? "key" : "i"), frameNo, fps, packCount, (frameNo - packCount), offset); }else{ // For non-steady frame rate, assume no offsets are used and the timestamp is already correct - VERYHIGH_MSG("Packing time %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. DTSC::Packet nextPack; @@ -893,7 +895,7 @@ namespace RTP{ } 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) if (!len){return;} @@ -976,12 +978,14 @@ namespace RTP{ offset = (frameNo - packCount) * (1000.0 / fps); //... and the timestamp is the packet counter times the frame rate in ms. newTs = packCount * (1000.0 / fps); - VERYHIGH_MSG("Packing time %llu = %sframe %llu (%.2f FPS). Expected %llu -> +%llu/%lu", ts, - isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset); + VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (%.2f FPS). Expected %" PRIu64 + " -> +%" PRIu64 "/%" PRIu32, + ts, isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset); }else{ // For non-steady frame rate, assume no offsets are used and the timestamp is already // correct - VERYHIGH_MSG("Packing time %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. DTSC::Packet nextPack; @@ -1007,11 +1011,13 @@ namespace RTP{ offset = (frameNo - packCount) * (1000.0 / fps); //... and the timestamp is the packet counter times the frame rate in ms. newTs = packCount * (1000.0 / fps); - VERYHIGH_MSG("Packing time %llu = %sframe %llu (%.2f FPS). Expected %llu -> +%llu/%lu", ts, - isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset); + VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (%.2f FPS). Expected %" PRIu64 + " -> +%" PRIu64 "/%" PRIu32, + ts, isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset); }else{ // For non-steady frame rate, assume no offsets are used and the timestamp is already correct - VERYHIGH_MSG("Packing time %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. DTSC::Packet nextPack; diff --git a/lib/rtp.h b/lib/rtp.h index f8414882..393ae387 100644 --- a/lib/rtp.h +++ b/lib/rtp.h @@ -25,7 +25,7 @@ namespace SDP{ /// This namespace holds all RTP-parsing and sending related functionality. 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 /// mechanisms, like increasing sequence numbers and setting timestamps are all taken care of in @@ -35,49 +35,47 @@ namespace RTP{ bool managed; char *data; ///< The actual RTP packet that is being sent uint32_t maxDataLen; ///< Amount of reserved bytes for the packet(s) - int sentPackets; - int sentBytes; // Because ugly is beautiful + uint32_t sentPackets; + uint32_t sentBytes; // Because ugly is beautiful public: static double startRTCP; - unsigned int getHsize() const; - unsigned int getPayloadSize() const; + uint32_t getHsize() const; + uint32_t getPayloadSize() const; char *getPayload() const; - unsigned int getVersion() const; - unsigned int getPadding() const; - unsigned int getExtension() const; - unsigned int getContribCount() const; - unsigned int getMarker() const; - unsigned int getPayloadType() const; - unsigned int getSequence() const; + uint32_t getVersion() const; + uint32_t getPadding() const; + uint32_t getExtension() const; + uint32_t getContribCount() const; + uint32_t getMarker() const; + uint32_t getPayloadType() const; + uint16_t getSequence() const; uint32_t getTimeStamp() const; - void setSequence(unsigned int seq); - unsigned int getSSRC() const; - void setSSRC(unsigned long ssrc); + void setSequence(uint16_t seq); + uint32_t getSSRC() const; + void setSSRC(uint32_t ssrc); void setTimestamp(uint32_t t); void increaseSequence(); - void sendH264(void *socket, void callBack(void *, char *, unsigned int, unsigned int), - const char *payload, unsigned int payloadlen, unsigned int channel, bool lastOfAccesUnit); - void sendVP8(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, + unsigned int payloadlen, unsigned int channel, bool lastOfAccessUnit); + void sendVP8(void *socket, void callBack(void *, const char *, size_t, uint8_t), 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); - 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); - void sendData(void *socket, void callBack(void *, char *, unsigned int, unsigned int), - const char *payload, unsigned int payloadlen, unsigned int channel, std::string codec); - void sendRTCP_SR(long long &connectedAt, void *socket, unsigned int tid, DTSC::Meta &metadata, - void callBack(void *, char *, unsigned int, unsigned int)); - void sendRTCP_RR(long long &connectedAt, SDP::Track &sTrk, unsigned int tid, - DTSC::Meta &metadata, void callBack(void *, char *, unsigned int, unsigned int)); + void sendData(void *socket, void callBack(void *, const char *, size_t, uint8_t), const char *payload, + unsigned int payloadlen, unsigned int channel, std::string codec); + void sendRTCP_SR(void *socket, void callBack(void *, const char *, size_t, uint8_t)); + void sendRTCP_RR(SDP::Track &sTrk, void callBack(void *, const char *, size_t, uint8_t)); 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); void operator=(const Packet &o); ~Packet(); - Packet(const char *dat, unsigned int len); - char *getData(); + Packet(const char *dat, uint64_t len); + const char *getData(); char *ptr() const{return data;} }; @@ -130,7 +128,7 @@ namespace RTP{ toDTSC(); void setProperties(const uint64_t track, const std::string &codec, const std::string &type, 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 (*cbInit)(const uint64_t track, const std::string &initData)); void addRTP(const RTP::Packet &rPkt); diff --git a/lib/rtp_fec.cpp b/lib/rtp_fec.cpp index 8c3abbef..a78ad617 100644 --- a/lib/rtp_fec.cpp +++ b/lib/rtp_fec.cpp @@ -240,7 +240,7 @@ namespace RTP{ size_t maskNumBytes = getNumBytesUsedForMask(); 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; } @@ -259,7 +259,7 @@ namespace RTP{ for (uint16_t byteDX = 0; byteDX < maskNumBytes; ++byteDX){ uint8_t maskByte = maskPtr[byteDX]; 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; coveredSeqNums.insert(seqNum); } @@ -502,7 +502,7 @@ namespace RTP{ Packet recreatedPacket; fec->tryToRecoverMissingPacket(packetHistory, recreatedPacket); 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", recreatedPacket.getSequence(), pl[0], pl[1], pl[2], pl[3], pl[4], pl[5], pl[6], pl[7]); addPacket(recreatedPacket); @@ -531,7 +531,7 @@ namespace RTP{ } 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); if (!rtcpData){ 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 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::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 + 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 - callBack(userData, rtcpData, 32); + callBack(userData, rtcpData, 32, 0); sorter.lostCurrent = 0; sorter.packCurrent = 0; free(rtcpData); diff --git a/lib/rtp_fec.h b/lib/rtp_fec.h index 6dd9288e..a73630a8 100644 --- a/lib/rtp_fec.h +++ b/lib/rtp_fec.h @@ -86,7 +86,7 @@ namespace RTP{ class FECPacket : public Packet{ public: 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 diff --git a/lib/sdp.cpp b/lib/sdp.cpp index 39081ee9..b8b97d39 100644 --- a/lib/sdp.cpp +++ b/lib/sdp.cpp @@ -45,24 +45,26 @@ namespace SDP{ } /// 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; - if (trk.codec == "H264"){ + + std::string codec = M.getCodec(tid); + std::string init = M.getInit(tid); + + if (codec == "H264"){ MP4::AVCC avccbox; - avccbox.setPayload(trk.init); + avccbox.setPayload(init); mediaDesc << "m=video 0 RTP/AVP 97\r\n" "a=rtpmap:97 H264/90000\r\n" "a=cliprect:0,0," - << trk.height << "," << trk.width - << "\r\n" - "a=framesize:97 " - << trk.width << '-' << trk.height + << M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:97 " + << M.getWidth(tid) << '-' << M.getHeight(tid) << "\r\n" "a=fmtp:97 packetization-mode=1;profile-level-id=" - << std::hex << std::setw(2) << std::setfill('0') << (int)trk.init.data()[1] - << std::dec << "E0" << std::hex << std::setw(2) << std::setfill('0') - << (int)trk.init.data()[3] << std::dec << ";" - << "sprop-parameter-sets="; + << std::hex << std::setw(2) << std::setfill('0') << (int)init.data()[1] << std::dec + << "E0" << std::hex << std::setw(2) << std::setfill('0') << (int)init.data()[3] + << std::dec << ";sprop-parameter-sets="; size_t count = avccbox.getSPSCount(); for (size_t i = 0; i < count; ++i){ mediaDesc << (i ? "," : "") @@ -75,19 +77,15 @@ namespace SDP{ << Encodings::Base64::encode(std::string(avccbox.getPPS(i), avccbox.getPPSLen(i))); } mediaDesc << "\r\n" - << "a=framerate:" << ((double)trk.fpks) / 1000.0 - << "\r\n" - "a=control:track" - << trk.trackID << "\r\n"; - }else if (trk.codec == "HEVC"){ - h265::initData iData(trk.init); + "a=framerate:" + << ((double)M.getFpks(tid)) / 1000.0 << "\r\na=control:track" << tid << "\r\n"; + }else if (codec == "HEVC"){ + h265::initData iData(init); mediaDesc << "m=video 0 RTP/AVP 104\r\n" "a=rtpmap:104 H265/90000\r\n" "a=cliprect:0,0," - << trk.height << "," << trk.width - << "\r\n" - "a=framesize:104 " - << trk.width << '-' << trk.height << "\r\n" + << M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:104 " + << M.getWidth(tid) << '-' << M.getHeight(tid) << "\r\n" << "a=fmtp:104 sprop-vps="; const std::set &vps = iData.getVPS(); if (vps.size()){ @@ -112,90 +110,80 @@ namespace SDP{ mediaDesc << Encodings::Base64::encode(*it); } } - mediaDesc << "\r\na=framerate:" << ((double)trk.fpks) / 1000.0 - << "\r\n" - "a=control:track" - << trk.trackID << "\r\n"; - }else if (trk.codec == "MPEG2"){ + mediaDesc << "\r\na=framerate:" << ((double)M.getFpks(tid)) / 1000.0 << "\r\na=control:track" + << tid << "\r\n"; + }else if (codec == "MPEG2"){ mediaDesc << "m=video 0 RTP/AVP 32\r\n" "a=cliprect:0,0," - << trk.height << "," << trk.width - << "\r\n" - "a=framesize:32 " - << trk.width << '-' << trk.height << "\r\n" - << "a=framerate:" << ((double)trk.fpks) / 1000.0 << "\r\n" - << "a=control:track" << trk.trackID << "\r\n"; - }else if (trk.codec == "AAC"){ + << M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:32 " << M.getWidth(tid) + << '-' << M.getHeight(tid) << "\r\na=framerate:" << ((double)M.getFpks(tid)) / 1000.0 + << "\r\na=control:track" << tid << "\r\n"; + }else if (codec == "AAC"){ mediaDesc << "m=audio 0 RTP/AVP 96" << "\r\n" "a=rtpmap:96 mpeg4-generic/" - << trk.rate << "/" << trk.channels + << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n" "a=fmtp:96 streamtype=5; profile-level-id=15; config="; - for (unsigned int i = 0; i < trk.init.size(); i++){ - mediaDesc << std::hex << std::setw(2) << std::setfill('0') << (int)trk.init[i] << std::dec; + for (unsigned int i = 0; i < init.size(); i++){ + mediaDesc << std::hex << std::setw(2) << std::setfill('0') << (int)init[i] << std::dec; } // these values are described in RFC 3640 mediaDesc << "; mode=AAC-hbr; SizeLength=13; IndexLength=3; IndexDeltaLength=3;\r\n" "a=control:track" - << trk.trackID << "\r\n"; - }else if (trk.codec == "MP3" || trk.codec == "MP2"){ - mediaDesc << "m=" << trk.type << " 0 RTP/AVP 14" + << tid << "\r\n"; + }else if (codec == "MP3" || codec == "MP2"){ + mediaDesc << "m=" << M.getType(tid) << " 0 RTP/AVP 14" << "\r\n" "a=rtpmap:14 MPA/90000/" - << trk.channels - << "\r\n" - "a=control:track" - << trk.trackID << "\r\n"; - }else if (trk.codec == "AC3"){ + << M.getChannels(tid) << "\r\n" + << "a=control:track" << tid << "\r\n"; + }else if (codec == "AC3"){ mediaDesc << "m=audio 0 RTP/AVP 100" << "\r\n" "a=rtpmap:100 AC3/" - << trk.rate << "/" << trk.channels - << "\r\n" - "a=control:track" - << trk.trackID << "\r\n"; - }else if (trk.codec == "ALAW"){ - if (trk.channels == 1 && trk.rate == 8000){ + << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n" + << "a=control:track" << tid << "\r\n"; + }else if (codec == "ALAW"){ + if (M.getChannels(tid) == 1 && M.getRate(tid) == 8000){ mediaDesc << "m=audio 0 RTP/AVP 8" << "\r\n"; }else{ mediaDesc << "m=audio 0 RTP/AVP 101" << "\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"; - }else if (trk.codec == "ULAW"){ - if (trk.channels == 1 && trk.rate == 8000){ + mediaDesc << "a=control:track" << tid << "\r\n"; + }else if (codec == "ULAW"){ + if (M.getChannels(tid) == 1 && M.getRate(tid) == 8000){ mediaDesc << "m=audio 0 RTP/AVP 0" << "\r\n"; }else{ mediaDesc << "m=audio 0 RTP/AVP 104" << "\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"; - }else if (trk.codec == "PCM"){ - if (trk.size == 16 && trk.channels == 2 && trk.rate == 44100){ + mediaDesc << "a=control:track" << tid << "\r\n"; + }else if (codec == "PCM"){ + if (M.getSize(tid) == 16 && M.getChannels(tid) == 2 && M.getRate(tid) == 44100){ mediaDesc << "m=audio 0 RTP/AVP 10" << "\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" << "\r\n"; }else{ mediaDesc << "m=audio 0 RTP/AVP 103" << "\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"; - }else if (trk.codec == "opus"){ + mediaDesc << "a=control:track" << tid << "\r\n"; + }else if (codec == "opus"){ mediaDesc << "m=audio 0 RTP/AVP 102" << "\r\n" "a=rtpmap:102 opus/" - << trk.rate << "/" << trk.channels - << "\r\n" - "a=control:track" - << trk.trackID << "\r\n"; + << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n" + << "a=control:track" << tid << "\r\n"; } return mediaDesc.str(); } @@ -232,43 +220,44 @@ namespace SDP{ /// \source The source identifier. /// \return True if successful, false otherwise. bool Track::parseTransport(const std::string &transport, const std::string &host, - const std::string &source, const DTSC::Track &trk){ - if (trk.codec == "H264"){ + const std::string &source, const DTSC::Meta *M, size_t tid){ + std::string codec = M->getCodec(tid); + if (codec == "H264"){ pack = RTP::Packet(97, 1, 0, mySSRC); - }else if (trk.codec == "HEVC"){ + }else if (codec == "HEVC"){ pack = RTP::Packet(104, 1, 0, mySSRC); - }else if (trk.codec == "MPEG2"){ + }else if (codec == "MPEG2"){ pack = RTP::Packet(32, 1, 0, mySSRC); - }else if (trk.codec == "AAC"){ + }else if (codec == "AAC"){ pack = RTP::Packet(96, 1, 0, mySSRC); - }else if (trk.codec == "AC3"){ + }else if (codec == "AC3"){ 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); - }else if (trk.codec == "ALAW"){ - if (trk.channels == 1 && trk.rate == 8000){ + }else if (codec == "ALAW"){ + if (M->getChannels(tid) == 1 && M->getRate(tid) == 8000){ pack = RTP::Packet(8, 1, 0, mySSRC); }else{ pack = RTP::Packet(101, 1, 0, mySSRC); } - }else if (trk.codec == "ULAW"){ - if (trk.channels == 1 && trk.rate == 8000){ + }else if (codec == "ULAW"){ + if (M->getChannels(tid) == 1 && M->getRate(tid) == 8000){ pack = RTP::Packet(0, 1, 0, mySSRC); }else{ pack = RTP::Packet(104, 1, 0, mySSRC); } - }else if (trk.codec == "PCM"){ - if (trk.size == 16 && trk.channels == 2 && trk.rate == 44100){ + }else if (codec == "PCM"){ + if (M->getSize(tid) == 16 && M->getChannels(tid) == 2 && M->getRate(tid) == 44100){ 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); }else{ pack = RTP::Packet(103, 1, 0, mySSRC); } - }else if (trk.codec == "opus"){ + }else if (codec == "opus"){ pack = RTP::Packet(102, 1, 0, mySSRC); }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; } 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). - 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; - rInfo << "url=" << source << "/track" << trk.trackID << ";"; // get the current url, not localhost - rInfo << "sequence=" << pack.getSequence() << ";rtptime=" << currentTime * getMultiplier(trk); + rInfo << "url=" << source << "/track" << tid << ";"; // get the current url, not localhost + rInfo << "seq=" << pack.getSequence() << ";rtptime=" << currentTime * getMultiplier(&M, tid); return rInfo.str(); } @@ -348,12 +337,11 @@ namespace 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::string to; - uint64_t trackNo = 0; + size_t tid = INVALID_TRACK_ID; bool nope = true; // true if we have no valid track to fill - DTSC::Track *thisTrack = 0; while (std::getline(ss, to, '\n')){ if (!to.empty() && *to.rbegin() == '\r'){to.erase(to.size() - 1, 1);} if (to.empty()){continue;} @@ -362,24 +350,23 @@ namespace SDP{ // All tracks start with a media line if (to.substr(0, 2) == "m="){ nope = true; - ++trackNo; - thisTrack = &(myMeta->tracks[trackNo]); + tid = myMeta->addTrack(); std::stringstream words(to.substr(2)); std::string item; if (getline(words, item, ' ') && (item == "audio" || item == "video")){ - thisTrack->type = item; - thisTrack->trackID = trackNo; + myMeta->setType(tid, item); + myMeta->setID(tid, tid); }else{ WARN_MSG("Media type not supported: %s", item.c_str()); - myMeta->tracks.erase(trackNo); - tracks.erase(trackNo); + myMeta->removeTrack(tid); + tracks.erase(tid); continue; } getline(words, item, ' '); if (!getline(words, item, ' ') || item.substr(0, 7) != "RTP/AVP"){ WARN_MSG("Media transport not supported: %s", item.c_str()); - myMeta->tracks.erase(trackNo); - tracks.erase(trackNo); + myMeta->removeTrack(tid); + tracks.erase(tid); continue; } if (getline(words, item, ' ')){ @@ -388,62 +375,62 @@ namespace SDP{ case 0: // PCM Mu-law INFO_MSG("PCM Mu-law payload type"); nope = false; - thisTrack->codec = "ULAW"; - thisTrack->rate = 8000; - thisTrack->channels = 1; + myMeta->setCodec(tid, "ULAW"); + myMeta->setRate(tid, 8000); + myMeta->setChannels(tid, 1); break; case 8: // PCM A-law INFO_MSG("PCM A-law payload type"); nope = false; - thisTrack->codec = "ALAW"; - thisTrack->rate = 8000; - thisTrack->channels = 1; + myMeta->setCodec(tid, "ALAW"); + myMeta->setRate(tid, 8000); + myMeta->setChannels(tid, 1); break; case 10: // PCM Stereo, 44.1kHz INFO_MSG("Linear PCM stereo 44.1kHz payload type"); nope = false; - thisTrack->codec = "PCM"; - thisTrack->size = 16; - thisTrack->rate = 44100; - thisTrack->channels = 2; + myMeta->setCodec(tid, "PCM"); + myMeta->setSize(tid, 16); + myMeta->setRate(tid, 44100); + myMeta->setChannels(tid, 2); break; case 11: // PCM Mono, 44.1kHz INFO_MSG("Linear PCM mono 44.1kHz payload type"); nope = false; - thisTrack->codec = "PCM"; - thisTrack->rate = 44100; - thisTrack->size = 16; - thisTrack->channels = 1; + myMeta->setCodec(tid, "PCM"); + myMeta->setRate(tid, 44100); + myMeta->setSize(tid, 16); + myMeta->setChannels(tid, 1); break; case 14: // MPA INFO_MSG("MPA payload type"); nope = false; - thisTrack->codec = "MP3"; - thisTrack->rate = 0; - thisTrack->size = 0; - thisTrack->channels = 0; + myMeta->setCodec(tid, "MP3"); + myMeta->setRate(tid, 0); + myMeta->setSize(tid, 0); + myMeta->setChannels(tid, 0); break; case 32: // MPV INFO_MSG("MPV payload type"); nope = false; - thisTrack->codec = "MPEG2"; + myMeta->setCodec(tid, "MPEG2"); break; default: // dynamic type 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; continue; }else{ - FAIL_MSG("Payload type %llu not supported!", avp_type); - myMeta->tracks.erase(trackNo); - tracks.erase(trackNo); + FAIL_MSG("Payload type %" PRIu64 " not supported!", avp_type); + myMeta->removeTrack(tid); + tracks.erase(tid); continue; } } } - tConv[trackNo].setProperties(*thisTrack); - HIGH_MSG("Incoming track %s", thisTrack->getIdentifier().c_str()); + tConv[tid].setProperties(*myMeta, tid); + HIGH_MSG("Incoming track %s", myMeta->getTrackIdentifier(tid).c_str()); continue; } @@ -456,62 +443,62 @@ namespace SDP{ for (unsigned int i = 0; i < trCodec.size(); ++i){ 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); if (extraInfo.find('/') != std::string::npos){ size_t lastSlash = extraInfo.find('/'); - thisTrack->rate = atoll(extraInfo.substr(0, lastSlash).c_str()); - thisTrack->channels = atoll(extraInfo.substr(lastSlash + 1).c_str()); + myMeta->setRate(tid, atoll(extraInfo.substr(0, lastSlash).c_str())); + myMeta->setChannels(tid, atoll(extraInfo.substr(lastSlash + 1).c_str())); }else{ - thisTrack->rate = atoll(extraInfo.c_str()); - thisTrack->channels = 1; + myMeta->setRate(tid, atoll(extraInfo.c_str())); + myMeta->setChannels(tid, 1); } } if (trCodec == "H264"){ - thisTrack->codec = "H264"; - thisTrack->rate = 90000; + myMeta->setCodec(tid, "H264"); + myMeta->setRate(tid, 90000); } if (trCodec == "H265"){ - thisTrack->codec = "HEVC"; - thisTrack->rate = 90000; + myMeta->setCodec(tid, "HEVC"); + myMeta->setRate(tid, 90000); } if (trCodec == "OPUS"){ - thisTrack->codec = "opus"; - thisTrack->init = std::string("OpusHead\001\002\170\000\200\273\000\000\000\000\000", 19); + myMeta->setCodec(tid, "opus"); + myMeta->setInit(tid, "OpusHead\001\002\170\000\200\273\000\000\000\000\000", 19); } - if (trCodec == "PCMA"){thisTrack->codec = "ALAW";} - if (trCodec == "PCMU"){thisTrack->codec = "ULAW";} + if (trCodec == "PCMA"){myMeta->setCodec(tid, "ALAW");} + if (trCodec == "PCMU"){myMeta->setCodec(tid, "ULAW");} if (trCodec == "L8"){ - thisTrack->codec = "PCM"; - thisTrack->size = 8; + myMeta->setCodec(tid, "PCM"); + myMeta->setSize(tid, 8); } if (trCodec == "L16"){ - thisTrack->codec = "PCM"; - thisTrack->size = 16; + myMeta->setCodec(tid, "PCM"); + myMeta->setSize(tid, 16); } if (trCodec == "L20"){ - thisTrack->codec = "PCM"; - thisTrack->size = 20; + myMeta->setCodec(tid, "PCM"); + myMeta->setSize(tid, 20); } if (trCodec == "L24" || trCodec == "PCM"){ - thisTrack->codec = "PCM"; - thisTrack->size = 24; + myMeta->setCodec(tid, "PCM"); + myMeta->setSize(tid, 24); } - if (trCodec == "MPEG4-GENERIC"){thisTrack->codec = "AAC";} - if (!thisTrack->codec.size()){ + if (trCodec == "MPEG4-GENERIC"){myMeta->setCodec(tid, "AAC");} + if (!myMeta->getCodec(tid).size()){ ERROR_MSG("Unsupported RTP mapping: %s", mediaType.c_str()); }else{ - tConv[trackNo].setProperties(*thisTrack); - HIGH_MSG("Incoming track %s", thisTrack->getIdentifier().c_str()); + tConv[tid].setProperties(*myMeta, tid); + HIGH_MSG("Incoming track %s", myMeta->getTrackIdentifier(tid).c_str()); } continue; } if (to.substr(0, 10) == "a=control:"){ - tracks[trackNo].control = to.substr(10); + tracks[tid].control = to.substr(10); continue; } 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; } if (to.substr(0, 12) == "a=framesize:"){ @@ -525,75 +512,76 @@ namespace SDP{ continue; } if (to.substr(0, 7) == "a=fmtp:"){ - tracks[trackNo].fmtp = to.substr(7); - if (thisTrack->codec == "AAC"){ - if (tracks[trackNo].getParamString("mode") != "AAC-hbr"){ + tracks[tid].fmtp = to.substr(7); + if (myMeta->getCodec(tid) == "AAC"){ + if (tracks[tid].getParamString("mode") != "AAC-hbr"){ // a=fmtp:97 // profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; // 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; - myMeta->tracks.erase(trackNo); - tracks.erase(trackNo); + myMeta->removeTrack(tid); + tracks.erase(tid); 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); } - if (thisTrack->codec == "H264"){ + if (myMeta->getCodec(tid) == "H264"){ // a=fmtp:96 packetization-mode=1; // sprop-parameter-sets=Z0LAHtkA2D3m//AUABqxAAADAAEAAAMAMg8WLkg=,aMuDyyA=; // 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(','); - tracks[trackNo].spsData = Encodings::Base64::decode(sprop.substr(0, comma)); - tracks[trackNo].ppsData = Encodings::Base64::decode(sprop.substr(comma + 1)); - updateH264Init(trackNo); + tracks[tid].spsData = Encodings::Base64::decode(sprop.substr(0, comma)); + tracks[tid].ppsData = Encodings::Base64::decode(sprop.substr(comma + 1)); + updateH264Init(tid); } - if (thisTrack->codec == "HEVC"){ - tracks[trackNo].hevcInfo.addUnit(Encodings::Base64::decode(tracks[trackNo].getParamString("sprop-vps"))); - tracks[trackNo].hevcInfo.addUnit(Encodings::Base64::decode(tracks[trackNo].getParamString("sprop-sps"))); - tracks[trackNo].hevcInfo.addUnit(Encodings::Base64::decode(tracks[trackNo].getParamString("sprop-pps"))); - updateH265Init(trackNo); + if (myMeta->getCodec(tid) == "HEVC"){ + tracks[tid].hevcInfo.addUnit( + Encodings::Base64::decode(tracks[tid].getParamString("sprop-vps"))); + tracks[tid].hevcInfo.addUnit( + Encodings::Base64::decode(tracks[tid].getParamString("sprop-sps"))); + tracks[tid].hevcInfo.addUnit( + Encodings::Base64::decode(tracks[tid].getParamString("sprop-pps"))); + updateH265Init(tid); } continue; } // We ignore bandwidth lines if (to.substr(0, 2) == "b="){continue;} // 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 - 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::iterator it = myMeta->tracks.begin(); - it != myMeta->tracks.end(); ++it){ - INFO_MSG("Detected track %s", it->second.getIdentifier().c_str()); + std::set validTracks = myMeta->getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + INFO_MSG("Detected track %s", myMeta->getTrackIdentifier(*it).c_str()); } } /// Calculates H265 track metadata from sps and pps data stored in tracks[trackNo] - void State::updateH265Init(uint64_t trackNo){ - DTSC::Track &Trk = myMeta->tracks[trackNo]; - SDP::Track &RTrk = tracks[trackNo]; + void State::updateH265Init(size_t tid){ + SDP::Track &RTrk = tracks[tid]; 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; } - 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; - Trk.width = MI.width; - Trk.height = MI.height; - Trk.fpks = RTrk.fpsMeta * 1000; - tConv[trackNo].setProperties(Trk); + myMeta->setWidth(tid, MI.width); + myMeta->setHeight(tid, MI.height); + myMeta->setFpks(tid, RTrk.fpsMeta * 1000); + tConv[tid].setProperties(*myMeta, tid); } /// Calculates H264 track metadata from vps, sps and pps data stored in tracks[trackNo] - void State::updateH264Init(uint64_t trackNo){ - DTSC::Track &Trk = myMeta->tracks[trackNo]; - SDP::Track &RTrk = tracks[trackNo]; + void State::updateH264Init(uint64_t tid){ + SDP::Track &RTrk = tracks[tid]; h264::sequenceParameterSet sps(RTrk.spsData.data(), RTrk.spsData.size()); h264::SPSMeta hMeta = sps.getCharacteristics(); MP4::AVCC avccBox; @@ -606,27 +594,27 @@ namespace SDP{ avccBox.setPPSCount(1); avccBox.setPPS(RTrk.ppsData); RTrk.fpsMeta = hMeta.fps; - Trk.width = hMeta.width; - Trk.height = hMeta.height; - Trk.fpks = hMeta.fps * 1000; - Trk.init = std::string(avccBox.payload(), avccBox.payloadSize()); - tConv[trackNo].setProperties(Trk); + myMeta->setWidth(tid, hMeta.width); + myMeta->setHeight(tid, hMeta.height); + myMeta->setFpks(tid, hMeta.fps * 1000); + myMeta->setInit(tid, avccBox.payload(), avccBox.payloadSize()); + tConv[tid].setProperties(*myMeta, tid); } - uint32_t State::getTrackNoForChannel(uint8_t chan){ - for (std::map::iterator it = tracks.begin(); it != tracks.end(); ++it){ + size_t State::getTrackNoForChannel(uint8_t chan){ + for (std::map::iterator it = tracks.begin(); it != tracks.end(); ++it){ 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; if (H.url == "200"){ ++trackCounter; - if (!tracks.count(trackCounter)){return 0;} - if (!tracks[trackCounter].parseTransport(H.GetHeader("Transport"), cH, src, myMeta->tracks[trackCounter])){ - return 0; + if (!tracks.count(trackCounter)){return INVALID_TRACK_ID;} + if (!tracks[trackCounter].parseTransport(H.GetHeader("Transport"), cH, src, myMeta, trackCounter)){ + return INVALID_TRACK_ID; } return trackCounter; } @@ -638,7 +626,7 @@ namespace SDP{ while (loop){ if (tracks.size()){ - for (std::map::iterator it = tracks.begin(); it != tracks.end(); ++it){ + for (std::map::iterator it = tracks.begin(); it != tracks.end(); ++it){ if (!it->second.control.size()){ it->second.control = "/track" + JSON::Value(it->first).asString(); INFO_MSG("Control track: %s", it->second.control.c_str()); @@ -649,8 +637,8 @@ namespace SDP{ (pw.size() >= it->second.control.size() && pw.substr(pw.size() - it->second.control.size()) == it->second.control)){ INFO_MSG("Parsing SETUP against track %lu", it->first); - if (!it->second.parseTransport(H.GetHeader("Transport"), cH, src, myMeta->tracks[it->first])){ - return 0; + if (!it->second.parseTransport(H.GetHeader("Transport"), cH, src, myMeta, it->first)){ + return INVALID_TRACK_ID; } return it->first; } @@ -658,13 +646,13 @@ namespace SDP{ } if (H.url.find("/track") != std::string::npos){ uint32_t trackNo = atoi(H.url.c_str() + H.url.find("/track") + 6); - if (trackNo){ - INFO_MSG("Parsing SETUP against track %lu", trackNo); - if (!tracks[trackNo].parseTransport(H.GetHeader("Transport"), cH, src, myMeta->tracks[trackNo])){ - return 0; - } - return trackNo; + // if (trackNo){ + INFO_MSG("Parsing SETUP against track %" PRIu32, trackNo); + if (!tracks[trackNo].parseTransport(H.GetHeader("Transport"), cH, src, myMeta, trackNo)){ + return INVALID_TRACK_ID; } + return trackNo; + //} } if (urlString != url.path){ urlString = url.path; @@ -672,18 +660,20 @@ namespace SDP{ loop = false; } } - return 0; + return INVALID_TRACK_ID; } /// Returns the multiplier to use to get milliseconds from the RTP payload type for the given /// track - double getMultiplier(const DTSC::Track &Trk){ - if (Trk.type == "video" || Trk.codec == "MP2" || Trk.codec == "MP3"){return 90.0;} - return ((double)Trk.rate / 1000.0); + double getMultiplier(const DTSC::Meta *M, size_t tid){ + if (M->getType(tid) == "video" || M->getCodec(tid) == "MP2" || M->getCodec(tid) == "MP3"){ + return 90.0; + } + return ((double)M->getRate(tid) / 1000.0); } - void State::updateInit(const uint64_t trackNo, const std::string &initData){ - if (myMeta->tracks.count(trackNo)){myMeta->tracks[trackNo].init = initData;} + void State::updateInit(const size_t tid, const std::string &initData){ + myMeta->setInit(tid, initData.data(), initData.size()); } /// Handles RTP packets generically, for both TCP and UDP-based connections. diff --git a/lib/sdp.h b/lib/sdp.h index 861e1a88..0de371c6 100644 --- a/lib/sdp.h +++ b/lib/sdp.h @@ -7,7 +7,7 @@ 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. class Track{ @@ -17,8 +17,8 @@ namespace SDP{ std::string getParamString(const std::string ¶m) const; uint64_t getParamInt(const std::string ¶m) const; bool parseTransport(const std::string &transport, const std::string &host, - const std::string &source, const DTSC::Track &trk); - std::string rtpInfo(const DTSC::Track &trk, const std::string &source, uint64_t currentTime); + const std::string &source, const DTSC::Meta *M, size_t tid); + std::string rtpInfo(const DTSC::Meta &M, size_t tid, const std::string &source, uint64_t currentTime); public: Socket::UDPConnection data; @@ -47,18 +47,18 @@ namespace SDP{ void (*incomingPacketCallback)(const DTSC::Packet &pkt); void parseSDP(const std::string &sdp); void parseSDPEx(const std::string &sdp); - void updateH264Init(uint64_t trackNo); - void updateH265Init(uint64_t trackNo); + void updateH264Init(size_t trackNo); + void updateH265Init(size_t tid); void updateInit(const uint64_t trackNo, const std::string &initData); - uint32_t getTrackNoForChannel(uint8_t chan); - uint32_t parseSetup(HTTP::Parser &H, const std::string &host, const std::string &source); + size_t getTrackNoForChannel(uint8_t chan); + size_t parseSetup(HTTP::Parser &H, const std::string &host, const std::string &source); void handleIncomingRTP(const uint64_t track, const RTP::Packet &pkt); public: DTSC::Meta *myMeta; - std::map tConv; ///< Converters to DTSC - std::map tracks; ///< List of selected tracks with SDP-specific session data. + std::map tConv; ///< Converters to DTSC + std::map 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 diff --git a/lib/sdp_media.cpp b/lib/sdp_media.cpp index b56bde3a..a603433c 100644 --- a/lib/sdp_media.cpp +++ b/lib/sdp_media.cpp @@ -488,7 +488,7 @@ namespace SDP{ MediaFormat *Media::getFormatForPayloadType(uint64_t &payloadType){ std::map::iterator it = formats.find(payloadType); 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 &it->second; @@ -581,6 +581,14 @@ namespace SDP{ 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){ if (sdp.empty()){ @@ -760,7 +768,7 @@ namespace SDP{ } Answer::Answer() - : isVideoEnabled(false), isAudioEnabled(false), candidatePort(0), + : isAudioEnabled(false), isVideoEnabled(false), candidatePort(0), videoLossPrevention(SDP_LOSS_PREVENTION_NONE){} bool Answer::parseOffer(const std::string &sdp){ @@ -820,45 +828,44 @@ namespace SDP{ direction = dir; } - bool Answer::setupVideoDTSCTrack(DTSC::Track &result){ - + bool Answer::setupVideoDTSCTrack(DTSC::Meta &M, size_t tid){ if (!isVideoEnabled){ FAIL_MSG("Video is disabled; cannot setup DTSC::Track."); return false; } - result.codec = codecRTP2Mist(answerVideoFormat.encodingName); - if (result.codec.empty()){ + M.setCodec(tid, codecRTP2Mist(answerVideoFormat.encodingName)); + if (M.getCodec(tid).empty()){ FAIL_MSG("Failed to convert the format codec into one that MistServer understands. %s.", answerVideoFormat.encodingName.c_str()); return false; } - - result.type = "video"; - result.rate = answerVideoFormat.getVideoRate(); - result.trackID = answerVideoFormat.payloadType; + M.setType(tid, "video"); + M.setRate(tid, answerVideoFormat.getVideoRate()); + M.setID(tid, answerVideoFormat.payloadType); + INFO_MSG("Setup video track %zu for payload type %zu", tid, answerVideoFormat.payloadType); return true; } - bool Answer::setupAudioDTSCTrack(DTSC::Track &result){ - + bool Answer::setupAudioDTSCTrack(DTSC::Meta &M, size_t tid){ if (!isAudioEnabled){ FAIL_MSG("Audio is disabled; cannot setup DTSC::Track."); return false; } - result.codec = codecRTP2Mist(answerAudioFormat.encodingName); - if (result.codec.empty()){ + M.setCodec(tid, codecRTP2Mist(answerAudioFormat.encodingName)); + if (M.getCodec(tid).empty()){ FAIL_MSG("Failed to convert the format codec into one that MistServer understands. %s.", answerAudioFormat.encodingName.c_str()); return false; } - result.type = "audio"; - result.rate = answerAudioFormat.getAudioSampleRate(); - result.channels = answerAudioFormat.getAudioNumChannels(); - result.size = answerAudioFormat.getAudioBitSize(); - result.trackID = answerAudioFormat.payloadType; + M.setType(tid, "audio"); + M.setRate(tid, answerAudioFormat.getAudioSampleRate()); + M.setChannels(tid, answerAudioFormat.getAudioNumChannels()); + M.setSize(tid, answerAudioFormat.getAudioBitSize()); + M.setID(tid, answerAudioFormat.payloadType); + INFO_MSG("Setup audio track %zu for payload time %zu", tid, answerAudioFormat.payloadType); return true; } @@ -1023,7 +1030,9 @@ namespace SDP{ 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] ={}; va_list args; diff --git a/lib/sdp_media.h b/lib/sdp_media.h index ccfac74e..f01914a4 100644 --- a/lib/sdp_media.h +++ b/lib/sdp_media.h @@ -50,7 +50,8 @@ namespace SDP{ uint64_t getPayloadType() const; ///< Returns the `payloadType` member. int32_t getPacketizationModeForH264(); ///< When this represents a h264 format this will return the ///< 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; @@ -144,6 +145,9 @@ namespace SDP{ 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 ///< 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: std::vector 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 ///< certificate that is used with DTLS. void setDirection(const std::string &dir); - bool setupVideoDTSCTrack(DTSC::Track &result); - bool setupAudioDTSCTrack(DTSC::Track &result); + bool setupVideoDTSCTrack(DTSC::Meta &M, size_t tid); + bool setupAudioDTSCTrack(DTSC::Meta &M, size_t tid); std::string toString(); private: bool enableMedia(const std::string &type, const std::string &codecName, SDP::Media &outMedia, SDP::MediaFormat &outFormat); - void addLine(const std::string &fmt, ...); + void addLine(const std::string fmt, ...); std::string generateSessionId(); std::string generateIceUFrag(); ///< Generates the `ice-ufrag` value. std::string generateIcePwd(); ///< Generates the `ice-pwd` value. diff --git a/lib/shared_memory.cpp b/lib/shared_memory.cpp index 52546f3f..cfaef807 100644 --- a/lib/shared_memory.cpp +++ b/lib/shared_memory.cpp @@ -22,11 +22,6 @@ #include #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{ #if defined(__CYGWIN__) || defined(_WIN32) @@ -48,8 +43,9 @@ namespace IPC{ ///\brief Constructs a named semaphore ///\param name The name of 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 value The initial value of 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 + /// 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){ #if defined(__CYGWIN__) || defined(_WIN32) mySem = 0; @@ -77,8 +73,9 @@ namespace IPC{ /// Closes currently opened semaphore if needed ///\param name The name of 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 value The initial value of 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 + /// 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){ close(); int timer = 0; @@ -139,7 +136,8 @@ namespace IPC{ int semaphore::getVal() const{ #if defined(__CYGWIN__) || defined(_WIN32) 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 int res; sem_getvalue(mySem, &res); @@ -202,7 +200,41 @@ namespace IPC{ 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(){ if (!(*this)){return false;} int result; @@ -598,581 +630,9 @@ namespace IPC{ ///\brief Default destructor 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 semGuard::semGuard(semaphore *thisSemaphore) : mySemaphore(thisSemaphore){mySemaphore->wait();} ///\brief Destructs a semaphore guard, unlocks the semaphore on call 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::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::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::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::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::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 diff --git a/lib/shared_memory.h b/lib/shared_memory.h index 749a3682..5be61915 100644 --- a/lib/shared_memory.h +++ b/lib/shared_memory.h @@ -20,49 +20,6 @@ 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 class semaphore{ public: @@ -75,6 +32,7 @@ namespace IPC{ void post(); void wait(); bool tryWait(); + bool tryWait(uint64_t ms); bool tryWaitOneSecond(); void close(); void abandon(); @@ -158,7 +116,7 @@ namespace IPC{ ///\brief The name of the opened shared memory page std::string name; ///\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 bool master; ///\brief A pointer to the payload of the page @@ -174,96 +132,4 @@ namespace IPC{ ~sharedPage(); }; #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 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 diff --git a/lib/srtp.h b/lib/srtp.h index 90399032..e3575a1a 100644 --- a/lib/srtp.h +++ b/lib/srtp.h @@ -15,8 +15,10 @@ public: SRTPReader(); int init(const std::string &cipher, const std::string &key, const std::string &salt); 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 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. */ + 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 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: srtp_t session; diff --git a/lib/stream.cpp b/lib/stream.cpp index 241777c0..2105d8a2 100644 --- a/lib/stream.cpp +++ b/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. /// Assumes the streamname has already been through sanitizeName()! 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")); } -std::set Util::findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, - const std::string &trackVal, const std::string &UA){ +/*LTS-START*/ +/// 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 Util::findTracks(const DTSC::Meta &M, const std::string &trackType, const std::string &trackVal){ std::set result; - if (!trackVal.size() || trackVal == "0" || trackVal == "-1" || trackVal == "none"){ - return result; - }// don't select anything in particular + if (!trackVal.size()){return result;} + if (trackVal == "-1" | trackVal == "none"){return result;}// don't select anything in particular if (trackVal.find(',') != std::string::npos){ // Comma-separated list, recurse. std::stringstream ss(trackVal); @@ -712,36 +702,27 @@ std::set Util::findTracks(const DTSC::Meta &M, const JSON::Value &capa, } return result; } - { - size_t trackNo = JSON::Value(trackVal).asInt(); - if (trackVal == JSON::Value((uint64_t)trackNo).asString()){ - // It's an integer number - 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); + size_t idx = JSON::Value(trackVal).asInt(); + if (trackVal == JSON::Value(idx).asString()){ + if (!M.trackValid(idx)){ + WARN_MSG("Track %zu does not exist in stream, cannot select", idx); 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; Util::stringToLower(trackLow); if (trackLow == "all" || trackLow == "*"){ // select all tracks of this type - std::set validTracks = getSupportedTracks(M, capa); + std::set validTracks = M.getValidTracks(); for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ - const DTSC::Track &Trk = M.tracks.at(*it); - if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ - result.insert(*it); - } + if (M.getType(*it) == trackType || M.getCodec(*it) == trackType){result.insert(*it);} } return result; } @@ -960,27 +941,12 @@ std::set Util::findTracks(const DTSC::Meta &M, const JSON::Value &capa, // attempt to do language/codec matching // convert 2-character language codes into 3-character language codes if (trackLow.size() == 2){trackLow = Encodings::ISO639::twoToThree(trackLow);} - std::set validTracks = getSupportedTracks(M, capa); + std::set validTracks = M.getValidTracks(); for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ - const DTSC::Track &Trk = M.tracks.at(*it); - if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ - std::string codecLow = Trk.codec; + if (M.getType(*it) == trackType || M.getCodec(*it) == trackType){ + std::string codecLow = M.getCodec(*it); Util::stringToLower(codecLow); - if (Trk.lang == 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);} - } - } + if (M.getLang(*it) == trackLow || trackLow == codecLow){result.insert(*it);} } } return result; @@ -996,14 +962,19 @@ std::set Util::wouldSelect(const DTSC::Meta &M, const std::string &track std::set Util::getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &type, const std::string &UA){ - std::set validTracks; - for (std::map::const_iterator it = M.tracks.begin(); it != M.tracks.end(); it++){ - const DTSC::Track &Trk = it->second; - if (type != "" && type != Trk.type){continue;} + std::set validTracks = M.getValidTracks(); + + std::set toRemove; + for (std::set::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 if (capa.isMember("codecs")){ - std::string codec = Trk.codec; - std::string type = Trk.type; + std::string codec = M.getCodec(*it); + std::string type = M.getType(*it); bool found = false; jsonForEachConst(capa["codecs"], itb){ jsonForEachConst(*itb, itc){ @@ -1040,11 +1011,30 @@ std::set Util::getSupportedTracks(const DTSC::Meta &M, const JSON::Value if (found){break;} } 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; } } - 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::iterator it = toRemove.begin(); it != toRemove.end(); it++){ + validTracks.erase(*it); } return validTracks; } @@ -1153,9 +1143,8 @@ std::set Util::wouldSelect(const DTSC::Meta &M, const std::map::iterator itd = result.begin(); itd != result.end(); itd++){ - const DTSC::Track &Trk = M.tracks.at(*itd); - if ((!byType && Trk.codec == strRef.substr(shift)) || - (byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + if ((!byType && M.getCodec(*itd) == strRef.substr(shift)) || + (byType && M.getType(*itd) == strRef.substr(shift)) || strRef.substr(shift) == "*"){ // user-agent-check bool problems = false; if (capa.isMember("exceptions") && capa["exceptions"].isObject() && @@ -1208,9 +1197,8 @@ std::set Util::wouldSelect(const DTSC::Meta &M, const std::map::iterator itd = result.begin(); itd != result.end(); itd++){ - const DTSC::Track &Trk = M.tracks.at(*itd); - if ((!byType && Trk.codec == strRef.substr(shift)) || - (byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + if ((!byType && M.getCodec(*itd) == strRef.substr(shift)) || + (byType && M.getType(*itd) == strRef.substr(shift)) || strRef.substr(shift) == "*"){ // user-agent-check bool problems = false; if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ @@ -1242,12 +1230,11 @@ std::set Util::wouldSelect(const DTSC::Meta &M, const std::map::reverse_iterator trit = validTracks.rbegin(); trit != validTracks.rend(); trit++){ - const DTSC::Track &Trk = M.tracks.at(*trit); - if ((!byType && Trk.codec == strRef.substr(shift)) || - (byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + if ((!byType && M.getCodec(*trit) == strRef.substr(shift)) || + (byType && M.getType(*trit) == strRef.substr(shift)) || strRef.substr(shift) == "*"){ // user-agent-check bool problems = false; if (capa.isMember("exceptions") && capa["exceptions"].isObject() && @@ -1259,12 +1246,15 @@ std::set Util::wouldSelect(const DTSC::Meta &M, const std::map Util::wouldSelect(const DTSC::Meta &M, const std::map::iterator trit = validTracks.begin(); trit != validTracks.end(); trit++){ - const DTSC::Track &Trk = M.tracks.at(*trit); - if ((!byType && Trk.codec == strRef.substr(shift)) || - (byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + if ((!byType && M.getCodec(*trit) == strRef.substr(shift)) || + (byType && M.getType(*trit) == strRef.substr(shift)) || strRef.substr(shift) == "*"){ // user-agent-check bool problems = false; if (capa.isMember("exceptions") && capa["exceptions"].isObject() && @@ -1287,12 +1276,15 @@ std::set Util::wouldSelect(const DTSC::Meta &M, const std::map -const JSON::Value empty; - namespace Util{ void streamVariables(std::string &str, const std::string &streamname, const std::string &source = ""); std::string getTmpFolder(); @@ -24,20 +22,17 @@ namespace Util{ JSON::Value getStreamConfig(const std::string &streamname); JSON::Value getGlobalConfig(const std::string &optionName); JSON::Value getInputBySource(const std::string &filename, bool isProvider = false); - DTSC::Meta getStreamMeta(const std::string &streamname); uint8_t getStreamStatus(const std::string &streamname); bool checkException(const JSON::Value &ex, const std::string &useragent); std::string codecString(const std::string &codec, const std::string &initData = ""); - std::set getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa = empty, + std::set getSupportedTracks(const DTSC::Meta &M, JSON::Value &capa, const std::string &type = "", const std::string &UA = ""); - std::set findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, - const std::string &trackVal, const std::string &UA = ""); + std::set findTracks(const DTSC::Meta &M, const std::string &trackType, const std::string &trackVal); std::set 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 wouldSelect(const DTSC::Meta &M, const std::map &targetParams, - const JSON::Value &capa = empty, const std::string &UA = "", - uint64_t seekTarget = 0); + JSON::Value capa = JSON::Value(), const std::string &UA = ""); class DTSCShmReader{ public: diff --git a/lib/stun.cpp b/lib/stun.cpp index 55abc12c..f5878dab 100644 --- a/lib/stun.cpp +++ b/lib/stun.cpp @@ -233,7 +233,7 @@ int stun_compute_hmac_sha1(uint8_t *message, uint32_t nbytes, std::string key, u 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); r = mbedtls_md_hmac_starts(&md_ctx, (const unsigned char *)key.c_str(), key.size()); diff --git a/lib/stun.h b/lib/stun.h index d339d8bc..3bc6af14 100644 --- a/lib/stun.h +++ b/lib/stun.h @@ -202,8 +202,10 @@ public: /* write header and finalize. call for each stun message */ int begin(StunMessage &msg, 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 - 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. */ + examples that can be found here + 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(); /* write attributes */ @@ -213,7 +215,9 @@ public: int writeUsername(const std::string &username); 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 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 */ uint8_t *getBufferPtr(); diff --git a/lib/triggers.cpp b/lib/triggers.cpp index 137dccc7..14a276d6 100644 --- a/lib/triggers.cpp +++ b/lib/triggers.cpp @@ -140,11 +140,10 @@ namespace Triggers{ 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 - ///\param type Trigger event type. - ///\param payload Trigger type-specific data - ///\param streamName The name of a stream. - ///\returns Boolean, false if further processing should be aborted. + ///\brief handles triggers for a specific trigger event type, with a payload, for a specified + /// stream, and/or server-wide \param type Trigger event type. \param payload Trigger + /// type-specific data \param streamName The name of a stream. \returns Boolean, false if further + /// processing should be aborted. /// calls doTrigger with dryRun set to false bool doTrigger(const std::string &type, const std::string &payload, const std::string &streamName){ usually_empty.clear(); @@ -158,14 +157,17 @@ namespace Triggers{ ///\param dryRun determines the mode of operation for this function ///\param response Returns the last received response by reference ///\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 - /// defined for that trigger event type. - /// The function 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 - /// trigger should be handled for the requested type/stream. - /// this can be used to make sure a payload is only generated if at least one trigger should be 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. + /// 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 defined for that trigger event type. The function + /// 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 trigger should be handled for the requested + /// type/stream. + /// this can be used to make sure a payload is only generated if at least one trigger should be + /// 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 dryRun, std::string &response, bool paramsCB(const char *, const void *), const void *extraParam){ diff --git a/lib/ts_packet.cpp b/lib/ts_packet.cpp index 159ab7ae..365a9ebc 100644 --- a/lib/ts_packet.cpp +++ b/lib/ts_packet.cpp @@ -30,7 +30,38 @@ std::set pmt_pids; std::map 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{ + 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. /// All this constructor does is call Packet::clear(). Packet::Packet(){ @@ -524,6 +555,35 @@ namespace TS{ } 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 /// Fills the free bytes of the Packet. @@ -1108,18 +1168,20 @@ namespace TS{ ///\param selectedTracks tracks to include in PMT creation ///\param myMeta ///\returns character pointer to a static 188B TS packet - const char *createPMT(std::set &selectedTracks, DTSC::Meta &myMeta, int contCounter){ + const char *createPMT(std::set &selectedTracks, const DTSC::Meta &M, int contCounter){ static ProgramMappingTable PMT; PMT.setPID(4096); PMT.setTableId(2); // section length met 2 tracks: 0xB017 int sectionLen = 0; for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + std::string codec = M.getCodec(*it); sectionLen += 5; - if (myMeta.tracks[*it].codec == "ID3"){sectionLen += myMeta.tracks[*it].init.size();} - if (myMeta.tracks[*it].codec == "AAC"){ + if (codec == "ID3" || codec == "RAW"){sectionLen += M.getInit(*it).size();} + if (codec == "AAC"){ 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 } } @@ -1133,42 +1195,51 @@ namespace TS{ PMT.setContinuityCounter(contCounter); int vidTrack = -1; for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - if (myMeta.tracks[*it].type == "video"){ + if (M.getType(*it) == "video"){ vidTrack = *it; break; } } 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); - short id = 0; ProgramMappingEntry entry = PMT.getEntry(0); for (std::set::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(""); - if (myMeta.tracks[*it].codec == "H264"){ + if (codec == "H264"){ entry.setStreamType(0x1B); - }else if (myMeta.tracks[*it].codec == "HEVC"){ + }else if (codec == "HEVC"){ entry.setStreamType(0x24); - }else if (myMeta.tracks[*it].codec == "MPEG2"){ + }else if (codec == "MPEG2"){ entry.setStreamType(0x02); - }else if (myMeta.tracks[*it].codec == "AAC"){ + }else if (codec == "AAC"){ 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 - 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(myMeta.tracks[*it].lang); + aac_info.append(lang); aac_info.append("\000", 1); } 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); - }else if (myMeta.tracks[*it].codec == "AC3"){ + }else if (codec == "AC3"){ entry.setStreamType(0x81); - }else if (myMeta.tracks[*it].codec == "ID3"){ + }else if (codec == "ID3"){ 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(); } @@ -1365,7 +1436,9 @@ namespace TS{ getOffset() + getSectionLength(); unsigned int newVal; // this will hold the CRC32 value; 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); strBuf[loc + 3] = (newVal >> 24) & 0xFF; strBuf[loc + 2] = (newVal >> 16) & 0xFF; diff --git a/lib/ts_packet.h b/lib/ts_packet.h index 158eaa59..730c19d3 100644 --- a/lib/ts_packet.h +++ b/lib/ts_packet.h @@ -75,6 +75,7 @@ namespace TS{ static std::string &getPESVideoLeadIn(unsigned int len, unsigned long long PTS, 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 &getPESMetaLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); // Printers and writers std::string toPrettyString(size_t indent = 0, int detailLevel = 3) const; @@ -242,37 +243,9 @@ namespace TS{ return std::string(StandardHeader, 7); } - /// 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 - 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}; + extern char PAT[188]; - const char *createPMT(std::set &selectedTracks, DTSC::Meta &myMeta, int contCounter = 0); + const char *createPMT(std::set &selectedTracks, const DTSC::Meta &M, int contCounter = 0); const char *createSDT(const std::string &streamName, int contCounter = 0); }// namespace TS diff --git a/lib/ts_stream.cpp b/lib/ts_stream.cpp index 00b3e825..725596d9 100644 --- a/lib/ts_stream.cpp +++ b/lib/ts_stream.cpp @@ -203,8 +203,9 @@ namespace TS{ case ID3: case MP2: case MPEG2: + case META: pidToCodec[pid] = sType; - if (sType == ID3){ + if (sType == ID3 || sType == META){ metaInit[pid] = std::string(entry.getESInfo(), entry.getESInfoLength()); } 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.back().genericFill(timeStamp, timeOffset, tid, pesPayload, realPayloadSize, bPos, 0); if (thisCodec == MP2 && !mp2Hdr.count(tid)){ @@ -607,7 +608,7 @@ namespace TS{ DTSC::Packet &bp = buildPacket[tid]; // Check if this is a keyframe - parseNal(tid, pesPayload, nextPtr, isKeyFrame); + parseNal(tid, pesPayload, pesPayload + nalSize, isKeyFrame); // If yes, set the keyframe flag if (isKeyFrame){bp.setKeyFrame(true);} @@ -651,10 +652,10 @@ namespace TS{ while (nextPtr < pesEnd && nalno < 8){ if (!nextPtr){nextPtr = pesEnd;} // 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 - parseNal(tid, pesPayload, nextPtr, isKeyFrame); + parseNal(tid, pesPayload, pesPayload + nalSize, isKeyFrame); ++nalno; 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 guard(tMutex); pack.null(); if (!hasPacket(tid)){ @@ -687,7 +688,7 @@ namespace TS{ return; } - pack = outPackets[tid].front(); + pack = DTSC::Packet(outPackets[tid].front(), mappedAs); outPackets[tid].pop_front(); 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){ tthread::lock_guard guard(tMutex); - size_t mId = mappingId; - for (std::map::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){ case H264:{ @@ -840,15 +845,11 @@ namespace TS{ MEDIUM_MSG("Aborted meta fill for h264 track %lu: no SPS/PPS", it->first); continue; } - meta.tracks[mId].type = "video"; - meta.tracks[mId].codec = "H264"; - meta.tracks[mId].trackID = mId; + // First generate needed data 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(); - meta.tracks[mId].width = spsChar.width; - meta.tracks[mId].height = spsChar.height; - meta.tracks[mId].fpks = spsChar.fps * 1000; + MP4::AVCC avccBox; avccBox.setVersion(1); avccBox.setProfile(spsInfo[it->first][1]); @@ -858,103 +859,111 @@ namespace TS{ avccBox.setSPS(spsInfo[it->first]); avccBox.setPPSCount(1); 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; case H265:{ if (!hevcInfo.count(it->first) || !hevcInfo[it->first].haveRequired()){ MEDIUM_MSG("Aborted meta fill for hevc track %lu: no info nal unit", it->first); continue; } - meta.tracks[mId].type = "video"; - meta.tracks[mId].codec = "HEVC"; - meta.tracks[mId].trackID = mId; - meta.tracks[mId].init = hevcInfo[it->first].generateHVCC(); + addNewTrack = true; + type = "video"; + codec = "HEVC"; + init = hevcInfo[it->first].generateHVCC(); h265::metaInfo metaInfo = hevcInfo[it->first].getMeta(); - meta.tracks[mId].width = metaInfo.width; - meta.tracks[mId].height = metaInfo.height; - meta.tracks[mId].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(); - } - } + width = metaInfo.width; + height = metaInfo.height; + fpks = metaInfo.fps * 1000; }break; case MPEG2:{ - meta.tracks[mId].type = "video"; - meta.tracks[mId].codec = "MPEG2"; - meta.tracks[mId].trackID = mId; - meta.tracks[mId].init = std::string("\000\000\001", 3) + mpeg2SeqHdr[it->first] + - std::string("\000\000\001", 3) + mpeg2SeqExt[it->first]; - - Mpeg::MPEG2Info info = Mpeg::parseMPEG2Header(meta.tracks[mId].init); - meta.tracks[mId].width = info.width; - meta.tracks[mId].height = info.height; - meta.tracks[mId].fpks = info.fps * 1000; + addNewTrack = true; + type = "video"; + codec = "MPEG2"; + init = std::string("\000\000\001", 3) + mpeg2SeqHdr[it->first] + + std::string("\000\000\001", 3) + mpeg2SeqExt[it->first]; + Mpeg::MPEG2Info info = Mpeg::parseMPEG2Header(init); + width = info.width; + height = info.height; + fpks = info.fps * 1000; }break; case ID3:{ - meta.tracks[mId].type = "meta"; - meta.tracks[mId].codec = "ID3"; - meta.tracks[mId].trackID = mId; - meta.tracks[mId].init = metaInit[it->first]; + addNewTrack = true; + type = "meta"; + codec = "ID3"; + init = metaInit[it->first]; + }break; + case META:{ + addNewTrack = true; + type = "meta"; + codec = "RAW"; + init = metaInit[it->first]; }break; case AC3:{ - meta.tracks[mId].type = "audio"; - meta.tracks[mId].codec = "AC3"; - meta.tracks[mId].trackID = mId; - meta.tracks[mId].size = 16; - ///\todo Fix these 2 values - meta.tracks[mId].rate = 0; - meta.tracks[mId].channels = 0; + addNewTrack = true; + type = "audio"; + codec = "AC3"; + size = 16; }break; case MP2:{ - meta.tracks[mId].type = "audio"; - meta.tracks[mId].codec = "MP2"; - meta.tracks[mId].trackID = mId; - + addNewTrack = true; Mpeg::MP2Info info = Mpeg::parseMP2Header(mp2Hdr[it->first]); - meta.tracks[mId].rate = info.sampleRate; - meta.tracks[mId].channels = info.channels; - - ///\todo Fix this value - meta.tracks[mId].size = 0; + type = "audio"; + codec = (info.layer == 3 ? "MP3" : "MP2"); + rate = info.sampleRate; + channels = info.channels; }break; case AAC:{ - meta.tracks[mId].type = "audio"; - meta.tracks[mId].codec = "AAC"; - meta.tracks[mId].trackID = mId; - meta.tracks[mId].size = 16; - meta.tracks[mId].rate = adtsInfo[it->first].getFrequency(); - meta.tracks[mId].channels = adtsInfo[it->first].getChannelCount(); - char audioInit[2]; // 5 bits object type, 4 bits frequency index, 4 bits channel index - audioInit[0] = ((adtsInfo[it->first].getAACProfile() & 0x1F) << 3) | - ((adtsInfo[it->first].getFrequencyIndex() & 0x0E) >> 1); - audioInit[1] = ((adtsInfo[it->first].getFrequencyIndex() & 0x01) << 7) | - ((adtsInfo[it->first].getChannelConfig() & 0x0F) << 3); - meta.tracks[mId].init = std::string(audioInit, 2); + addNewTrack = true; + init.resize(2); + init[0] = ((adtsInfo[it->first].getAACProfile() & 0x1F) << 3) | + ((adtsInfo[it->first].getFrequencyIndex() & 0x0E) >> 1); + init[1] = ((adtsInfo[it->first].getFrequencyIndex() & 0x01) << 7) | + ((adtsInfo[it->first].getChannelConfig() & 0x0F) << 3); + + type = "audio"; + codec = "AAC"; + size = 16; + rate = adtsInfo[it->first].getFrequency(); + channels = adtsInfo[it->first].getChannelCount(); }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(); for (size_t i = 0; i < pmtCount; i++){ uint32_t 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(); + meta.setLang(idx, ProgramDescriptors(entry.getESInfo(), entry.getESInfoLength()).getLanguage()); } entry.advance(); } } - MEDIUM_MSG("Initialized track %lu as %s %s", it->first, meta.tracks[mId].codec.c_str(), - meta.tracks[mId].type.c_str()); + MEDIUM_MSG("Initialized track %lu as %s %s", idx, codec.c_str(), type.c_str()); } } @@ -983,7 +992,8 @@ namespace TS{ case AC3: case ID3: case MP2: - case MPEG2: result.insert(entry.getElementaryPid()); break; + case MPEG2: + case META: result.insert(entry.getElementaryPid()); break; default: break; } entry.advance(); diff --git a/lib/ts_stream.h b/lib/ts_stream.h index b3321e13..6050935c 100644 --- a/lib/ts_stream.h +++ b/lib/ts_stream.h @@ -18,7 +18,8 @@ namespace TS{ H265 = 0x24, ID3 = 0x15, MPEG2 = 0x02, - MP2 = 0x03 + MP2 = 0x03, + META = 0x06 }; class ADTSRemainder{ @@ -57,10 +58,10 @@ namespace TS{ bool hasPacketOnEachTrack() const; bool hasPacket(size_t tid) 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(); 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 clear(); void finish(); diff --git a/lib/util.cpp b/lib/util.cpp index acc87c90..0f8796dd 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -396,6 +396,7 @@ namespace Util{ void FieldAccX::set(const std::string &val, size_t recordNo){ char *place = src->getPointer(field, recordNo); memcpy(place, val.data(), std::min((size_t)field.size, val.size())); + place[std::min((size_t)field.size - 1, val.size())] = 0; } /// If 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){ + if (!fields.count(fName)){return FieldAccX();} return FieldAccX(this, fields.at(fName)); } RelAccXFieldData RelAccX::getFieldData(const std::string &fName) const{ + if (!fields.count(fName)){return RelAccXFieldData();} return fields.at(fName); } }// namespace Util diff --git a/lib/util.h b/lib/util.h index ddff19e5..2d244792 100644 --- a/lib/util.h +++ b/lib/util.h @@ -63,7 +63,11 @@ namespace Util{ uint8_t type; uint32_t size; uint32_t offset; - RelAccXFieldData(){} + RelAccXFieldData(){ + type = 0; + size = 0; + offset = 0; + } RelAccXFieldData(uint8_t t, uint32_t s, uint32_t o){ type = t; size = s; diff --git a/src/analysers/analyser.cpp b/src/analysers/analyser.cpp index c63d87aa..4838f1ac 100644 --- a/src/analysers/analyser.cpp +++ b/src/analysers/analyser.cpp @@ -67,10 +67,10 @@ int Analyser::run(Util::Config &conf){ if (validate && ((finTime - upTime + 10) * 1000 < mediaTime)){ uint32_t sleepMs = mediaTime - (Util::bootSecs() - upTime + 10) * 1000; 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; } - INFO_MSG("Sleeping for %lums", sleepMs); + INFO_MSG("Sleeping for %" PRIu32 "ms", sleepMs); Util::sleep(sleepMs); finTime = Util::bootSecs(); } @@ -80,7 +80,7 @@ int Analyser::run(Util::Config &conf){ return 4; } if ((finTime - upTime) >= timeOut){ - WARN_MSG("Reached timeout of %llu seconds, stopping", timeOut); + WARN_MSG("Reached timeout of %" PRIu64 " seconds, stopping", timeOut); return 3; } } diff --git a/src/analysers/analyser_dash.cpp b/src/analysers/analyser_dash.cpp index 3c1db50b..881db8a8 100644 --- a/src/analysers/analyser_dash.cpp +++ b/src/analysers/analyser_dash.cpp @@ -30,17 +30,14 @@ bool getDelimBlock(std::string &data, std::string name, size_t &blockStart, size 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; } @@ -50,15 +47,12 @@ bool getValueBlock(std::string &data, std::string name, size_t &blockStart, size 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; } @@ -67,23 +61,17 @@ bool getString(std::string &data, std::string name, std::string &output){ 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; } @@ -96,7 +84,7 @@ bool getBlock(std::string &data, std::string name, int offset, size_t &blockStar } 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; } @@ -104,75 +92,71 @@ bool getBlock(std::string &data, std::string name, int offset, size_t &blockStar if (blockEnd == std::string::npos){ blockEnd = data.find("/>", blockStart); if (blockEnd == std::string::npos){ - DEBUG_MSG(DLVL_INFO, "no block end found."); + INFO_MSG("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!! + 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 - 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; } - // 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 ¤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"); + 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. + FAIL_MSG("mimeType not found"); 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("audio") != std::string::npos){tempSD.trackType = AUDIO;} 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; } // find an ID within this adaptationSet block. if (!getBlock(data, (std::string) "Representation", offset, blockStart, blockEnd)){ - DEBUG_MSG(DLVL_FAIL, "Representation not found"); + FAIL_MSG("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? + INFO_MSG("Representation block: %s", block.c_str()); + ///\todo 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()); + FAIL_MSG("Representation id not found in block %s", block.c_str()); return false; } - DEBUG_MSG(DLVL_INFO, "Representation/id: %li", tempSD.trackID); // checked, OK + INFO_MSG("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"); + FAIL_MSG("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); @@ -181,20 +165,19 @@ bool parseAdaptationSet(std::string &data, std::set ¤tPos){ 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()); + FAIL_MSG("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()); + FAIL_MSG("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()); + FAIL_MSG("Failed to find and replace $RepresentationID$ in %s", tempSD.initialization.c_str()); return false; } tempSD.initialization.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d"); @@ -202,12 +185,12 @@ bool parseAdaptationSet(std::string &data, std::set ¤tPos){ // 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"); + FAIL_MSG("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 + std::string block2 = block.substr(blockStart, + (blockEnd - blockStart)); // overwrites previous block (takes just the segmentTimeline part int numS = 0; offset = 0; @@ -216,10 +199,10 @@ bool parseAdaptationSet(std::string &data, std::set ¤tPos){ while (1){ if (!getBlock(block2, "S", offset, blockStart, blockEnd)){ if (numS == 0){ - DEBUG_MSG(DLVL_FAIL, "no S found within SegmentTimeline"); + FAIL_MSG("no S found within SegmentTimeline"); return false; }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) } } @@ -227,16 +210,14 @@ bool parseAdaptationSet(std::string &data, std::set ¤tPos){ // 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"); + FAIL_MSG("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; @@ -249,7 +230,6 @@ bool parseAdaptationSet(std::string &data, std::set ¤tPos){ 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 @@ -265,30 +245,30 @@ bool parseXML(std::string &body, std::set ¤tPos, std::vector 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. + FAIL_MSG("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."); + FAIL_MSG("no adaptationSet found."); return false; } - DEBUG_MSG(DLVL_INFO, "all adaptation sets found. total: %i", numAdaptationSet); + INFO_MSG("all adaptation sets found. total: %i", numAdaptationSet); return true; } @@ -297,7 +277,7 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){ url = conf.getString("url"); 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; exit(1); } @@ -329,9 +309,9 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){ } // 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); - 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);} pos = 0; @@ -346,13 +326,8 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){ currentPos; 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(in)), std::istreambuf_iterator()); 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"){ long long int endTime = Util::bootSecs(); std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl; @@ -362,30 +337,30 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){ } H.Clean(); - DEBUG_MSG(DLVL_INFO, "*********"); - DEBUG_MSG(DLVL_INFO, "*SUMMARY*"); - DEBUG_MSG(DLVL_INFO, "*********"); + INFO_MSG("*********"); + INFO_MSG("*SUMMARY*"); + 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++){ - 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()); + INFO_MSG(""); + INFO_MSG("ID in vector %d", i); + INFO_MSG("trackID %ld", streamData[i].trackID); + INFO_MSG("adaptationSet %d", streamData[i].adaptationSet); + INFO_MSG("trackType (audio 0x02, video 0x01) %d", streamData[i].trackType); + INFO_MSG("TimeScale %ld", streamData[i].timeScale); + INFO_MSG("Media string %s", streamData[i].media.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 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()); + INFO_MSG("init url for adaptationSet %d trackID %ld: %s ", streamData[i].adaptationSet, + streamData[i].trackID, streamData[i].initURL.c_str()); } } @@ -403,8 +378,7 @@ dashAnalyser::~dashAnalyser(){ int dashAnalyser::doAnalyse(){ - // DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str()); - // match adaptation set and track id? + ///\todo 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 && @@ -415,20 +389,16 @@ int dashAnalyser::doAnalyse(){ 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); + INFO_MSG("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: "< currentPos; std::vector 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(in)), std::istreambuf_iterator()); 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"){ long long int endTime = Util::bootSecs(); std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl; @@ -566,37 +528,36 @@ int main2(int argc, char **argv){ } H.Clean(); - DEBUG_MSG(DLVL_INFO, "*********"); - DEBUG_MSG(DLVL_INFO, "*SUMMARY*"); - DEBUG_MSG(DLVL_INFO, "*********"); + INFO_MSG("*********"); + INFO_MSG("*SUMMARY*"); + 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++){ - 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()); + INFO_MSG(""); + INFO_MSG("ID in vector %d", i); + INFO_MSG("trackID %ld", streamData[i].trackID); + INFO_MSG("adaptationSet %d", streamData[i].adaptationSet); + INFO_MSG("trackType (audio 0x02, video 0x01) %d", streamData[i].trackType); + INFO_MSG("TimeScale %ld", streamData[i].timeScale); + INFO_MSG("Media string %s", streamData[i].media.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 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()); + INFO_MSG("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()); std::cout << "blaa" << std::endl; - // match adaptation set and track id? + ///\todo 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 && @@ -607,18 +568,15 @@ int main2(int argc, char **argv){ 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); + INFO_MSG("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: "<= 3){ std::cout << "DTSC header: "; - DTSC::Meta(P).toPrettyString( - std::cout, 0, (detail == 3 ? 0 : (detail == 4 ? 0x01 : (detail == 5 ? 0x03 : 0x07)))); + std::cout << DTSC::Meta("", P.getScan()).toPrettyString(); } if (detail == 2){std::cout << "DTSC header: " << P.getScan().toPrettyString() << std::endl;} if (detail == 1){ @@ -79,30 +78,34 @@ bool AnalyserDTSC::parsePacket(){ bool hasAAC = false; JSON::Value result; std::stringstream issues; - DTSC::Meta M(P); - for (std::map::iterator it = M.tracks.begin(); it != M.tracks.end(); it++){ + DTSC::Meta M("", P.getScan()); + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + std::string codec = M.getCodec(*it); JSON::Value track; - track["kbits"] = (uint64_t)((double)it->second.bps * 8 / 1024); - track["codec"] = it->second.codec; + track["kbits"] = M.getBps(*it) * 8 / 1024; + track["codec"] = codec; uint32_t shrtest_key = 0xFFFFFFFFul; uint32_t longest_key = 0; uint32_t shrtest_prt = 0xFFFFFFFFul; uint32_t longest_prt = 0; uint32_t shrtest_cnt = 0xFFFFFFFFul; uint32_t longest_cnt = 0; - for (std::deque::iterator k = it->second.keys.begin(); k != it->second.keys.end(); k++){ - if (!k->getLength()){continue;} - if (k->getLength() > longest_key){longest_key = k->getLength();} - if (k->getLength() < shrtest_key){shrtest_key = k->getLength();} - if (k->getParts() > longest_cnt){longest_cnt = k->getParts();} - if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();} - if (k->getParts()){ - if ((k->getLength() / k->getParts()) > longest_prt){ - longest_prt = (k->getLength() / k->getParts()); - } - if ((k->getLength() / k->getParts()) < shrtest_prt){ - shrtest_prt = (k->getLength() / k->getParts()); - } + + DTSC::Keys keys(M.keys(*it)); + uint32_t firstKey = keys.getFirstValid(); + uint32_t endKey = keys.getEndValid(); + for (int i = firstKey; i < endKey; i++){ + uint64_t keyDuration = keys.getDuration(i); + uint64_t keyParts = keys.getParts(i); + if (!keyDuration){continue;} + if (keyDuration > longest_key){longest_key = keyDuration;} + if (keyDuration < shrtest_key){shrtest_key = keyDuration;} + if (keyParts > longest_cnt){longest_cnt = keyParts;} + 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; @@ -112,28 +115,28 @@ bool AnalyserDTSC::parsePacket(){ track["keys"]["frames_min"] = shrtest_cnt; track["keys"]["frames_max"] = longest_cnt; 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){ - 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 (it->second.codec == "H264"){hasH264 = true;} - if (it->second.type == "video"){ - track["width"] = it->second.width; - track["height"] = it->second.height; - track["fpks"] = it->second.fpks; - if (it->second.codec == "H264"){ + if (codec == "AAC"){hasAAC = true;} + if (codec == "H264"){hasH264 = true;} + if (M.getType(*it) == "video"){ + track["width"] = M.getWidth(*it); + track["height"] = M.getHeight(*it); + track["fpks"] = M.getFpks(*it); + if (codec == "H264"){ h264::sequenceParameterSet sps; - sps.fromDTSCInit(it->second.init); + sps.fromDTSCInit(M.getInit(*it)); h264::SPSMeta spsData = sps.getCharacteristics(); track["h264"]["profile"] = spsData.profile; 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 (!hasH264){issues << "HLS no video!";} } @@ -147,7 +150,7 @@ bool AnalyserDTSC::parsePacket(){ if (detail >= 2){std::cout << "DTCM command: " << P.getScan().toPrettyString() << std::endl;} break; } - default: FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes); break; + default: FAIL_MSG("Invalid DTSC packet @ byte %" PRIu64, totalBytes); break; } totalBytes += P.getDataLen(); diff --git a/src/analysers/analyser_ebml.cpp b/src/analysers/analyser_ebml.cpp index 2f6c7b6b..45154635 100644 --- a/src/analysers/analyser_ebml.cpp +++ b/src/analysers/analyser_ebml.cpp @@ -35,7 +35,7 @@ bool AnalyserEBML::parsePacket(){ if (dataBuffer.size() < neededBytes()){return false;} 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);} switch (E.getID()){ case EBML::EID_SEGMENT: diff --git a/src/analysers/analyser_ebml.h b/src/analysers/analyser_ebml.h index 3a51e855..f803e9a1 100644 --- a/src/analysers/analyser_ebml.h +++ b/src/analysers/analyser_ebml.h @@ -11,7 +11,7 @@ private: uint64_t neededBytes(); std::string dataBuffer; uint64_t curPos; - uint64_t prePos; + size_t prePos; uint64_t segmentOffset; uint32_t lastSeekId; uint64_t lastSeekPos; diff --git a/src/analysers/analyser_flv.cpp b/src/analysers/analyser_flv.cpp index 578fbdb0..18442d5d 100644 --- a/src/analysers/analyser_flv.cpp +++ b/src/analysers/analyser_flv.cpp @@ -32,7 +32,8 @@ bool AnalyserFLV::parsePacket(){ // If we arrive here, we've loaded a FLV packet 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(); return true; diff --git a/src/analysers/analyser_flv.h b/src/analysers/analyser_flv.h index d88485f3..b7168382 100644 --- a/src/analysers/analyser_flv.h +++ b/src/analysers/analyser_flv.h @@ -9,5 +9,5 @@ public: private: FLV::Tag flvData; - long long filter; + int64_t filter; }; diff --git a/src/analysers/analyser_h264.cpp b/src/analysers/analyser_h264.cpp index a9dbc09e..595ce5f9 100644 --- a/src/analysers/analyser_h264.cpp +++ b/src/analysers/analyser_h264.cpp @@ -39,7 +39,7 @@ bool AnalyserH264::parsePacket(){ size = 0; nalPtr = h264::nalFactory(dataBuffer.data(), dataBuffer.size(), size, !sizePrepended); 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);} dataBuffer.erase(0, size); // erase the NAL unit we just read prePos += size; @@ -47,7 +47,7 @@ bool AnalyserH264::parsePacket(){ ///\TODO update mediaTime with current timestamp }while (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 true; diff --git a/src/analysers/analyser_hls.cpp b/src/analysers/analyser_hls.cpp index 68d4d115..d185d558 100644 --- a/src/analysers/analyser_hls.cpp +++ b/src/analysers/analyser_hls.cpp @@ -40,7 +40,7 @@ void AnalyserHLS::getParts(const std::string &body){ } if (!parsedPart || no > parsedPart){ 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)); } ++no; @@ -111,7 +111,7 @@ bool AnalyserHLS::parsePacket(){ } } 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; } parsedPart = part.no; @@ -124,8 +124,8 @@ bool AnalyserHLS::parsePacket(){ // Hm. I guess we had no parts to get. if (refreshAt && refreshAt > Util::bootSecs()){ // We're getting a live stream. Let's wait and check again. - uint32_t sleepSecs = (refreshAt - Util::bootSecs()); - INFO_MSG("Sleeping for %lu seconds", sleepSecs); + uint64_t sleepSecs = (refreshAt - Util::bootSecs()); + INFO_MSG("Sleeping for %" PRIu64 " seconds", sleepSecs); Util::sleep(sleepSecs * 1000); } // The non-live case is already handled in isOpen() diff --git a/src/analysers/analyser_mp4.cpp b/src/analysers/analyser_mp4.cpp index ce31318e..4ac928e4 100644 --- a/src/analysers/analyser_mp4.cpp +++ b/src/analysers/analyser_mp4.cpp @@ -1,4 +1,5 @@ #include "analyser_mp4.h" +#include void AnalyserMP4::init(Util::Config &conf){ Analyser::init(conf); @@ -22,23 +23,21 @@ bool AnalyserMP4::parsePacket(){ } 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;} ///\TODO update mediaTime with the current timestamp return true; } - FAIL_MSG("Could not read box at position %llu", prePos); + FAIL_MSG("Could not read box at position %" PRIu64, prePos); return false; } /// Calculates how many bytes we need to read a whole box. uint64_t AnalyserMP4::neededBytes(){ 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 (mp4Buffer.size() < 16){return 16;} - size = 0 + ntohl(((int *)mp4Buffer.data())[2]); - size <<= 32; - size += ntohl(((int *)mp4Buffer.data())[3]); + size = Bit::btohll(mp4Buffer.data() + 8); return size; } diff --git a/src/analysers/analyser_ogg.cpp b/src/analysers/analyser_ogg.cpp index 23c16bb1..c4774a82 100644 --- a/src/analysers/analyser_ogg.cpp +++ b/src/analysers/analyser_ogg.cpp @@ -28,18 +28,18 @@ bool AnalyserOGG::parsePacket(){ sn2Codec[oggPage.getBitstreamSerialNumber()] = "Opus"; } 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()); }else{ - WARN_MSG("Bitstream %llu not recognized!", oggPage.getBitstreamSerialNumber()); + WARN_MSG("Bitstream %" PRIu64 " not recognized!", oggPage.getBitstreamSerialNumber()); } } if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){ if (detail >= 2){std::cout << " Theora data" << std::endl;} - static unsigned int numParts = 0; - static unsigned int keyCount = 0; - for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){ + static size_t numParts = 0; + static size_t keyCount = 0; + for (size_t i = 0; i < oggPage.getAllSegments().size(); i++){ theora::header tmpHeader((char *)oggPage.getSegment(i), oggPage.getAllSegments()[i].size()); if (tmpHeader.isHeader()){ if (tmpHeader.getHeaderType() == 0){kfgshift = tmpHeader.getKFGShift();} diff --git a/src/analysers/analyser_ogg.h b/src/analysers/analyser_ogg.h index 4350170a..adc8f12b 100644 --- a/src/analysers/analyser_ogg.h +++ b/src/analysers/analyser_ogg.h @@ -8,8 +8,8 @@ public: static void init(Util::Config &conf); private: - std::map sn2Codec; + std::map sn2Codec; std::string oggBuffer; OGG::Page oggPage; - int kfgshift; + uint16_t kfgshift; }; diff --git a/src/analysers/analyser_riff.cpp b/src/analysers/analyser_riff.cpp index 0c40b365..76c74606 100644 --- a/src/analysers/analyser_riff.cpp +++ b/src/analysers/analyser_riff.cpp @@ -26,7 +26,7 @@ bool AnalyserRIFF::parsePacket(){ if (dataBuffer.size() < 8){return false;} 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);} ///\TODO update mediaTime with the current timestamp if (C){ diff --git a/src/analysers/analyser_rtmp.cpp b/src/analysers/analyser_rtmp.cpp index 660b8866..d2af848f 100644 --- a/src/analysers/analyser_rtmp.cpp +++ b/src/analysers/analyser_rtmp.cpp @@ -2,6 +2,7 @@ /// Debugging tool for RTMP data. #include "analyser_rtmp.h" +#include void AnalyserRTMP::init(Util::Config &conf){ Analyser::init(conf); @@ -42,23 +43,19 @@ bool AnalyserRTMP::parsePacket(){ // While we can't parse a packet, while (!next.Parse(strbuf)){ // fill our internal buffer "strbuf" in (up to) 1024 byte chunks - if (std::cin.good()){ - unsigned int charCount = 0; - std::string tmpbuffer; - tmpbuffer.reserve(1024); - while (std::cin.good() && charCount < 1024){ - char newchar = std::cin.get(); - if (std::cin.good()){ - tmpbuffer += newchar; - ++read_in; - ++charCount; - } + if (!std::cin.good()){return false;} + size_t charCount = 0; + std::string tmpbuffer; + tmpbuffer.reserve(1024); + while (std::cin.good() && charCount < 1024){ + char newchar = std::cin.get(); + if (std::cin.good()){ + tmpbuffer += newchar; + ++read_in; + ++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 @@ -72,71 +69,66 @@ bool AnalyserRTMP::parsePacket(){ break; // happens when connection breaks unexpectedly case 1: // set chunk size 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; 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 break; case 3: // ack - RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str()); - DETAIL_MED("CTRL: Acknowledgement: %i", RTMPStream::snd_window_at); + RTMPStream::snd_window_at = Bit::btohl(next.data.data()); + DETAIL_MED("CTRL: Acknowledgement: %" PRIu64, RTMPStream::snd_window_at); break; case 4:{ - short int ucmtype = ntohs(*(short int *)next.data.c_str()); + int16_t ucmtype = Bit::btohs(next.data.data()); switch (ucmtype){ case 0: - DETAIL_MED("CTRL: User control message: stream begin %u", - ntohl(*(unsigned int *)(next.data.c_str() + 2))); + DETAIL_MED("CTRL: User control message: stream begin %" PRIu32, Bit::btohl(next.data.data() + 2)); break; 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; 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; case 3: - DETAIL_MED("CTRL: User control message: setbufferlen %u", - ntohl(*(unsigned int *)(next.data.c_str() + 2))); + DETAIL_MED("CTRL: User control message: setbufferlen %" PRIu32, Bit::btohl(next.data.data() + 2)); break; case 4: - DETAIL_MED("CTRL: User control message: streamisrecorded %u", - ntohl(*(unsigned int *)(next.data.c_str() + 2))); + DETAIL_MED("CTRL: User control message: streamisrecorded %" PRIu32, Bit::btohl(next.data.data() + 2)); break; case 6: - DETAIL_MED("CTRL: User control message: pingrequest %u", - ntohl(*(unsigned int *)(next.data.c_str() + 2))); + DETAIL_MED("CTRL: User control message: pingrequest %" PRIu32, Bit::btohl(next.data.data() + 2)); break; case 7: - DETAIL_MED("CTRL: User control message: pingresponse %u", - ntohl(*(unsigned int *)(next.data.c_str() + 2))); + DETAIL_MED("CTRL: User control message: pingresponse %" PRIu32, Bit::btohl(next.data.data() + 2)); break; case 31: case 32: - // don't know, but not interesting anyway + // don't know, but not interes ting anyway break; default: - DETAIL_LOW("CTRL: User control message: UNKNOWN %hu - %u", ucmtype, - ntohl(*(unsigned int *)(next.data.c_str() + 2))); + DETAIL_LOW("CTRL: User control message: UNKNOWN %" PRId16 " - %" PRIu32, ucmtype, + Bit::btohl(next.data.data() + 2)); break; } }break; 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; - DETAIL_MED("CTRL: Window size: %i", RTMPStream::rec_window_size); + DETAIL_MED("CTRL: Window size: %" PRIu64, RTMPStream::rec_window_size); break; 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) - DETAIL_MED("CTRL: Set peer bandwidth: %i", RTMPStream::snd_window_size); + DETAIL_MED("CTRL: Set peer bandwidth: %" PRIu64, RTMPStream::snd_window_size); break; case 8: case 9: if (detail >= 4 || reconstruct.good() || validate){ F.ChunkLoader(next); 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);} } break; diff --git a/src/analysers/analyser_rtmp.h b/src/analysers/analyser_rtmp.h index 435d691d..2627027d 100644 --- a/src/analysers/analyser_rtmp.h +++ b/src/analysers/analyser_rtmp.h @@ -7,7 +7,7 @@ class AnalyserRTMP : public Analyser{ private: RTMPStream::Chunk next; ///< Holds the most recently parsed RTMP chunk 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 AMF::Object amfdata; ///< Last read AMF object AMF::Object3 amf3data; ///< Last read AMF3 object diff --git a/src/analysers/analyser_rtsp.cpp b/src/analysers/analyser_rtsp.cpp index 1d72cdcf..1e1c85ed 100644 --- a/src/analysers/analyser_rtsp.cpp +++ b/src/analysers/analyser_rtsp.cpp @@ -14,9 +14,9 @@ void AnalyserRTSP::incoming(const DTSC::Packet &pkt){ char *dataPtr; size_t 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(), - myMeta.tracks[pkt.getTrackId()].getIdentifier().c_str(), pkt.getTime()); + myMeta.getTrackIdentifier(pkt.getTrackId()).c_str(), pkt.getTime()); if (detail >= 8){ for (uint32_t i = 0; i < dataSize; ++i){ std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)dataPtr[i] << " "; @@ -58,9 +58,9 @@ bool AnalyserRTSP::parsePacket(){ return true; } if (HTTP.hasHeader("Transport")){ - uint32_t trackNo = sdpState.parseSetup(HTTP, "", ""); + size_t trackNo = sdpState.parseSetup(HTTP, "", ""); if (trackNo){ - DETAIL_MED("Parsed transport for track: %lu", trackNo); + DETAIL_MED("Parsed transport for track: %zu", trackNo); }else{ DETAIL_MED("Could not parse transport string!"); } @@ -95,15 +95,15 @@ bool AnalyserRTSP::parsePacket(){ RTP::Packet pkt(tcpPacket.data() + 4, len); uint8_t chan = tcpHead.data()[1]; uint32_t trackNo = sdpState.getTrackNoForChannel(chan); - DETAIL_HI("Received %ub RTP packet #%u on channel %u, time %llu", len, - (unsigned int)pkt.getSequence(), chan, pkt.getTimeStamp()); + DETAIL_HI("Received %ub RTP packet #%u on channel %u, time %" PRIu32, len, pkt.getSequence(), + chan, pkt.getTimeStamp()); if (!trackNo && (chan % 2) != 1){ DETAIL_MED("Received packet for unknown track number on channel %u", chan); } if (trackNo){sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();} if (detail >= 10){ - char *pl = pkt.getPayload(); + const char *pl = pkt.getPayload(); uint32_t payLen = pkt.getPayloadSize(); for (uint32_t i = 0; i < payLen; ++i){ std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)pl[i] << " "; diff --git a/src/analysers/analyser_ts.cpp b/src/analysers/analyser_ts.cpp index b93b0c9a..4f31aef9 100644 --- a/src/analysers/analyser_ts.cpp +++ b/src/analysers/analyser_ts.cpp @@ -46,7 +46,7 @@ bool AnalyserTS::parsePacket(){ static char packetPtr[188]; std::cin.read(packetPtr, 188); if (std::cin.gcount() != 188){return false;} - DONTEVEN_MSG("Reading from position %llu", bytes); + DONTEVEN_MSG("Reading from position %" PRIu64, bytes); bytes += 188; if (!packet.FromPointer(packetPtr)){return false;} if (detail){ @@ -74,15 +74,15 @@ bool AnalyserTS::parsePacket(){ } AnalyserTS::~AnalyserTS(){ - for (std::map::iterator it = payloads.begin(); it != payloads.end(); it++){ + for (std::map::iterator it = payloads.begin(); it != payloads.end(); it++){ if ((detail & 1) && (!pidOnly || it->first == pidOnly)){ std::cout << printPES(it->second, it->first); } } } -std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){ - unsigned int headSize = 0; +std::string AnalyserTS::printPES(const std::string &d, size_t PID){ + size_t headSize = 0; std::stringstream res; bool known = false; 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){ res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2] << " ]"; } - unsigned int padding = 0; + size_t padding = 0; if (known){ if ((d[6] & 0xC0) != 0x80){res << " [!INVALID FIRST BITS!]";} 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]"; } if (timeFlags & 0x02){ - long long unsigned int time = (((unsigned int)d[9] & 0xE) >> 1); + uint64_t time = ((d[9] & 0xE) >> 1); 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 |= ((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]"; } if (timeFlags & 0x01){ - long long unsigned int time = ((d[14] >> 1) & 0x07); + uint64_t time = ((d[14] >> 1) & 0x07); time <<= 15; - time |= ((int)d[15] << 7) | (d[16] >> 1); + time |= ((uint32_t)d[15] << 7) | (d[16] >> 1); 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]"; } } @@ -169,12 +169,18 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){ res << std::endl; if (detail & 32){ - unsigned int counter = 0; - for (unsigned int i = 9 + headSize + padding; i < d.size(); ++i){ + size_t counter = 0; + 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){ res << std::endl; 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) << " "; if ((counter) % 32 == 31){res << std::endl;} counter++; diff --git a/src/analysers/analyser_ts.h b/src/analysers/analyser_ts.h index 9bdf27c8..4257e99d 100644 --- a/src/analysers/analyser_ts.h +++ b/src/analysers/analyser_ts.h @@ -8,11 +8,11 @@ public: ~AnalyserTS(); bool parsePacket(); 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: - std::map payloads; - uint32_t pidOnly; + std::map payloads; + size_t pidOnly; TS::Packet packet; uint64_t bytes; }; diff --git a/src/analysers/dash_analyser.cpp b/src/analysers/dash_analyser.cpp deleted file mode 100644 index 8237dc26..00000000 --- a/src/analysers/dash_analyser.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include - -#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 and - } - - 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 - 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 ¤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 ¤tPos, std::vector &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 - 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 currentPos; - std::vector 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(in)), std::istreambuf_iterator()); - 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: "<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; -} diff --git a/src/analysers/h264_translate.cpp b/src/analysers/h264_translate.cpp new file mode 100644 index 00000000..72782c1d --- /dev/null +++ b/src/analysers/h264_translate.cpp @@ -0,0 +1,78 @@ +#include +#include +#include + +#include +#include +#include +#include + +///\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); +} diff --git a/src/controller/controller.cpp b/src/controller/controller.cpp index 5118fb0c..e2350faf 100644 --- a/src/controller/controller.cpp +++ b/src/controller/controller.cpp @@ -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"); // Generate instanceId once per boot. if (Controller::instanceId == ""){ diff --git a/src/controller/controller_capabilities.cpp b/src/controller/controller_capabilities.cpp index e2aed216..0643b9ec 100644 --- a/src/controller/controller_capabilities.cpp +++ b/src/controller/controller_capabilities.cpp @@ -95,17 +95,11 @@ namespace Controller{ trgs["STREAM_PUSH"]["response"] = "always"; 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["STREAM_TRACK_ADD"]["stream_specific"] = true; - trgs["STREAM_TRACK_ADD"]["payload"] = "stream name (string)\ntrack ID (integer)\n"; - trgs["STREAM_TRACK_ADD"]["response"] = "ignored"; - trgs["STREAM_TRACK_ADD"]["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["LIVE_TRACK_LIST"]["when"] = "After the list of valid tracks has been updated"; + trgs["LIVE_TRACK_LIST"]["stream_specific"] = true; + trgs["LIVE_TRACK_LIST"]["payload"] = "stream name (string)\ntrack list (JSON)\n"; + trgs["LIVE_TRACK_LIST"]["response"] = "ignored"; + trgs["LIVE_TRACK_LIST"]["response_action"] = "None."; trgs["STREAM_BUFFER"]["when"] = "Every time a live stream buffer changes state"; trgs["STREAM_BUFFER"]["stream_specific"] = true; diff --git a/src/controller/controller_connectors.cpp b/src/controller/controller_connectors.cpp index 6bbb9c79..a7fcd56e 100644 --- a/src/controller/controller_connectors.cpp +++ b/src/controller/controller_connectors.cpp @@ -145,17 +145,18 @@ namespace Controller{ if (pipedCapa.isMember("optional")){builPipedPart(p, argarr, argnum, pipedCapa["optional"]);} } - ///\brief Checks current protocol configuration, updates state of enabled connectors if neccessary. - ///\param p An object containing all protocols. - ///\param capabilities An object containing the detected capabilities. - ///\returns True if any action was taken + ///\brief Checks current protocol configuration, updates state of enabled connectors if + /// neccessary. \param p An object containing all protocols. \param capabilities An object + /// containing the detected capabilities. \returns True if any action was taken /// /// \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 /// ~~~~~~~~~~~~~~~ - /// 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 /// ~~~~~~~~~~~~~~~ diff --git a/src/controller/controller_license.cpp b/src/controller/controller_license.cpp index 0db39558..a5a535b6 100644 --- a/src/controller/controller_license.cpp +++ b/src/controller/controller_license.cpp @@ -118,11 +118,12 @@ namespace Controller{ for (unsigned int i = 0; i < 8; ++i){ 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", - Encodings::Base64::encode(Encryption::AES_Crypt( - RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec))); + Encodings::Base64::encode(crypter.encryptBlockCTR(0, RELEASE "|" PACKAGE_VERSION))); } if (!dl.get(url) || !dl.isOk()){return;} response = JSON::fromString(dl.data()); @@ -143,11 +144,12 @@ namespace Controller{ aesKey[15 - i] = ((licID >> (i * 8)) + aesKey[15 - i]) & 0xFF; } std::string cipher = Encodings::Base64::decode(input); - std::string deCrypted; // magic ivecs, they are empty. It's secretly 16 times \0. - char ivec[16]; - memset(ivec, 0, 16); - deCrypted = Encryption::AES_Crypt(cipher.c_str(), cipher.size(), aesKey, ivec); + Encryption::AES crypter; + crypter.setEncryptKey(aesKey); + // 0 here for 0-filled ivec. + std::string deCrypted = crypter.encryptBlockCTR(0, cipher); + // get time stamps and license. // verify checksum diff --git a/src/controller/controller_push.cpp b/src/controller/controller_push.cpp index 55b043c1..5017ad9a 100644 --- a/src/controller/controller_push.cpp +++ b/src/controller/controller_push.cpp @@ -135,7 +135,10 @@ namespace Controller{ void pushCheckLoop(void *np){ { 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; IPC::sharedPage pushPage("MstPush", 8 * 1024 * 1024, true, false); diff --git a/src/controller/controller_statistics.cpp b/src/controller/controller_statistics.cpp index ba720698..c4c86090 100644 --- a/src/controller/controller_statistics.cpp +++ b/src/controller/controller_statistics.cpp @@ -30,6 +30,7 @@ #define STAT_CLI_BPS_DOWN 128 #define STAT_CLI_BPS_UP 256 #define STAT_CLI_CRC 512 +#define STAT_CLI_SESSID 1024 #define STAT_CLI_ALL 0xFFFF // These are used to store "totals" field requests in a bitfield for speedup. #define STAT_TOT_CLIENTS 1 @@ -48,6 +49,8 @@ std::map Controller::triggerStats; ///< Hol bool Controller::killOnExit = KILL_ON_EXIT; tthread::mutex Controller::statsMutex; unsigned int Controller::maxConnsPerIP = 0; +uint64_t Controller::statDropoff = 0; + char noBWCountMatches[1717]; 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 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(){ 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::stringstream s; s << ID << "(" << host << " " << crc << " " << streamName << " " << connector << ")"; 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{ 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()); } -/// \todo Make this prettier. -IPC::sharedServer *statPointer = 0; +Comms::Statistics statComm; +bool statCommActive = false; /// Invalidates all current sessions for the given streamname /// Updates the session cache, afterwards. void Controller::sessions_invalidate(const std::string &streamname){ - if (!statPointer){ + if (!statCommActive){ FAIL_MSG("In shutdown procedure - cannot invalidate sessions."); return; } @@ -209,7 +204,7 @@ void Controller::sessions_shutdown(JSON::Iter &i){ /// Shuts down the given session /// Updates the session cache, afterwards. void Controller::sessId_shutdown(const std::string &sessId){ - if (!statPointer){ + if (!statCommActive){ FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions."); return; } @@ -231,7 +226,7 @@ void Controller::sessId_shutdown(const std::string &sessId){ /// Tags the given session 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."); 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 /// Updates the session cache, afterwards. void Controller::tag_shutdown(const std::string &tag){ - if (!statPointer){ + if (!statCommActive){ FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions."); return; } @@ -272,7 +267,7 @@ void Controller::tag_shutdown(const std::string &tag){ /// Shuts down all current sessions for the given streamname /// Updates the session cache, afterwards. 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."); return; } @@ -325,9 +320,13 @@ void Controller::writeSessionCache(){ /// old statistics that have disconnected over 10 minutes ago. void Controller::SharedMemStats(void *config){ HIGH_MSG("Starting stats thread"); - IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true); - statPointer = &statServer; - shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, true); + statComm.reload(true); + statCommActive = 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->unlink(); cacheLock->open(SEM_SESSCACHE, O_CREAT | O_RDWR, ACCESSPERMS, 1); @@ -341,7 +340,10 @@ void Controller::SharedMemStats(void *config){ tthread::lock_guard guard2(statsMutex); cacheLock->wait(); /*LTS*/ // parse current users - statServer.parseEach(parseStatistics); + statLeadIn(); + COMM_LOOP(statComm, statOnActive(id), statOnDisconnect(id)); + statLeadOut(); + if (firstRun){ firstRun = false; servUpOtherBytes = 0; @@ -357,18 +359,27 @@ void Controller::SharedMemStats(void *config){ // wipe old statistics if (sessions.size()){ std::list mustWipe; - unsigned long long cutOffPoint = Util::epoch() - STAT_CUTOFF; - unsigned long long disconnectPointIn = Util::epoch() - STATS_INPUT_DELAY; - unsigned long long disconnectPointOut = Util::epoch() - STATS_DELAY; + uint64_t cutOffPoint = Util::bootSecs() - STAT_CUTOFF; + uint64_t disconnectPointIn = Util::bootSecs() - STATS_INPUT_DELAY; + uint64_t disconnectPointOut = Util::bootSecs() - STATS_DELAY; for (std::map::iterator it = sessions.begin(); it != sessions.end(); it++){ - unsigned long long dPoint = it->second.getSessType() == SESS_INPUT ? disconnectPointIn : disconnectPointOut; - it->second.ping(it->first, dPoint); + uint64_t dPoint = it->second.getSessType() == SESS_INPUT ? disconnectPointIn : disconnectPointOut; if (it->second.sync == 100){ + // Denied entries are connection-entry-wiped as soon as they become boring it->second.wipeOld(dPoint); }else{ + // Normal entries are summarized after STAT_CUTOFF seconds 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()){ sessions.erase(mustWipe.front()); @@ -429,15 +440,18 @@ void Controller::SharedMemStats(void *config){ } Util::wait(1000); } - statPointer = 0; + statCommActive = false; HIGH_MSG("Stopping stats thread"); if (Util::Config::is_restarting){ - statServer.abandon(); + statComm.setMaster(false); shmSessions->master = false; }else{/*LTS-START*/ if (Controller::killOnExit){ 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*/ } @@ -474,12 +488,10 @@ std::set Controller::getActiveStreams(const std::string &prefix){ uint32_t Controller::statSession::invalidate(){ uint32_t ret = 0; sync = 1; - if (curConns.size() && statPointer){ + if (curConns.size() && statCommActive){ for (std::map::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){ - char *data = statPointer->getIndex(jt->first); - if (data){ - IPC::statExchange tmpEx(data); - tmpEx.setSync(2); + if (statComm.getStatus(jt->first) != COMM_STATUS_INVALID){ + statComm.setSync(2, jt->first); ret++; } } @@ -492,16 +504,14 @@ uint32_t Controller::statSession::invalidate(){ uint32_t Controller::statSession::kill(){ uint32_t ret = 0; sync = 100; - if (curConns.size() && statPointer){ + if (curConns.size() && statCommActive){ for (std::map::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){ - char *data = statPointer->getIndex(jt->first); - if (data){ - IPC::statExchange tmpEx(data); - tmpEx.setSync(100); - uint32_t pid = tmpEx.getPID(); + if (statComm.getStatus(jt->first) != COMM_STATUS_INVALID){ + statComm.setSync(100, jt->first); + uint32_t pid = statComm.getPid(jt->first); if (pid > 1){ Util::Procs::Stop(pid); - INFO_MSG("Killing PID %lu", pid); + INFO_MSG("Killing PID %" PRIu32, pid); } ret++; } @@ -511,15 +521,18 @@ uint32_t Controller::statSession::kill(){ } /// Updates the given active connection with new stats data. -void Controller::statSession::update(uint64_t index, IPC::statExchange &data){ - // update the sync byte: 0 = requesting fill, 2 = requesting refill, 1 = needs checking, > 2 = state known (100=denied, 10=accepted) - if (!data.getSync()){ - sessIndex tmpidx(data); - std::string myHost = tmpidx.host; +void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm){ + std::string myHost = statComm.getHost(index); + std::string myStream = statComm.getStream(index); + std::string myConnector = statComm.getConnector(index); + // 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 (maxConnsPerIP && !data.getSync()){ + if (maxConnsPerIP && !statComm.getSync(index)){ unsigned int currConns = 1; - long long shortly = Util::epoch(); + long long shortly = Util::bootSecs(); for (std::map::iterator it = sessions.begin(); it != sessions.end(); it++){ 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){ 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 // 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){ - MEDIUM_MSG("Requesting sync to %u for %s, %s, %s, %lu", sync, data.streamName().c_str(), - data.connector().c_str(), myHost.c_str(), data.crc() & 0xFFFFFFFFu); - data.setSync(sync); + MEDIUM_MSG("Requesting sync to %u for %s, %s, %s, %" PRIu32, sync, myStream.c_str(), + myConnector.c_str(), myHost.c_str(), statComm.getCRC(index) & 0xFFFFFFFFu); + statComm.setSync(sync, index); } // and, always set the sync if it is > 2 if (sync > 2){ - MEDIUM_MSG("Setting sync to %u for %s, %s, %s, %lu", sync, data.streamName().c_str(), - data.connector().c_str(), myHost.c_str(), data.crc() & 0xFFFFFFFFu); - data.setSync(sync); + MEDIUM_MSG("Setting sync to %u for %s, %s, %s, %" PRIu32, sync, myStream.c_str(), + myConnector.c_str(), myHost.c_str(), statComm.getCRC(index) & 0xFFFFFFFFu); + statComm.setSync(sync, index); } } }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 prevUp = getUp(); - curConns[index].update(data); + curConns[index].update(statComm, index); // 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 - if (data.now() > lastSec){ - lastSec = data.now(); + if (statComm.getNow(index) > lastSec){ + lastSec = statComm.getNow(index); if (!tracked){ tracked = true; firstActive = firstSec; @@ -571,13 +584,13 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){ long long currUp = getUp(); if (currUp - prevUp < 0 || currDown - prevDown < 0){ 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{ if (!noBWCount){ size_t bwMatchOffset = 0; noBWCount = 1; 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])){ noBWCount = 2; break; @@ -599,40 +612,39 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){ } } if (currDown + currUp >= COUNTABLE_BYTES){ - std::string streamName = data.streamName(); if (sessionType == SESS_UNSET){ - if (data.connector() == "INPUT"){ + if (myConnector == "INPUT"){ ++servInputs; - streamStats[streamName].inputs++; - streamStats[streamName].currIns++; + streamStats[myStream].inputs++; + streamStats[myStream].currIns++; sessionType = SESS_INPUT; - }else if (data.connector() == "OUTPUT"){ + }else if (myConnector == "OUTPUT"){ ++servOutputs; - streamStats[streamName].outputs++; - streamStats[streamName].currOuts++; + streamStats[myStream].outputs++; + streamStats[myStream].currOuts++; sessionType = SESS_OUTPUT; }else{ ++servViewers; - streamStats[streamName].viewers++; - streamStats[streamName].currViews++; + streamStats[myStream].viewers++; + streamStats[myStream].currViews++; sessionType = SESS_VIEWER; } } // 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. if (prevUp + prevDown < COUNTABLE_BYTES){ - if (!streamName.size() || streamName[0] == 0){ - if (streamStats.count(streamName)){streamStats.erase(streamName);} + if (!myStream.size() || myStream[0] == 0){ + if (streamStats.count(myStream)){streamStats.erase(myStream);} }else{ - streamStats[streamName].upBytes += currUp; - streamStats[streamName].downBytes += currDown; + streamStats[myStream].upBytes += currUp; + streamStats[myStream].downBytes += currDown; } }else{ - if (!streamName.size() || streamName[0] == 0){ - if (streamStats.count(streamName)){streamStats.erase(streamName);} + if (!myStream.size() || myStream[0] == 0){ + if (streamStats.count(myStream)){streamStats.erase(myStream);} }else{ - streamStats[streamName].upBytes += currUp - prevUp; - streamStats[streamName].downBytes += currDown - prevDown; + streamStats[myStream].upBytes += currUp - prevUp; + streamStats[myStream].downBytes += currDown - prevDown; } } } @@ -642,7 +654,7 @@ Controller::sessType Controller::statSession::getSessType(){ return sessionType; } -/// Archives the given connection. +/// Archives connection log entries older than the given cutOff point. void Controller::statSession::wipeOld(uint64_t cutOff){ if (firstSec > cutOff){return;} 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 (lastSec < disconnectPoint){ - switch (sessionType){ - case SESS_INPUT: - if (streamStats[index.streamName].currIns){streamStats[index.streamName].currIns--;} - break; - case SESS_OUTPUT: - if (streamStats[index.streamName].currOuts){streamStats[index.streamName].currOuts--;} - break; - case SESS_VIEWER: - if (streamStats[index.streamName].currViews){streamStats[index.streamName].currViews--;} - break; - default: break; - } - uint64_t duration = lastSec - firstActive; - if (duration < 1){duration = 1;} - std::stringstream tagStream; - if (tags.size()){ - for (std::set::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; + switch (sessionType){ + case SESS_INPUT: + if (streamStats[index.streamName].currIns){streamStats[index.streamName].currIns--;} + break; + case SESS_OUTPUT: + if (streamStats[index.streamName].currOuts){streamStats[index.streamName].currOuts--;} + break; + case SESS_VIEWER: + if (streamStats[index.streamName].currViews){streamStats[index.streamName].currViews--;} + break; + default: break; } + uint64_t duration = lastSec - firstActive; + if (duration < 1){duration = 1;} + std::stringstream tagStream; + if (tags.size()){ + for (std::set::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. @@ -836,16 +846,12 @@ bool Controller::statSession::hasDataFor(uint64_t t){ /// Returns true if there is any data for this session. bool Controller::statSession::hasData(){ if (!firstSec && !lastSec){return false;} + if (curConns.size()){return true;} if (oldConns.size()){ for (std::deque::iterator it = oldConns.begin(); it != oldConns.end(); ++it){ if (it->log.size()){return true;} } } - if (curConns.size()){ - for (std::map::iterator it = curConns.begin(); it != curConns.end(); ++it){ - if (it->second.log.size()){return true;} - } - } return false; } @@ -854,26 +860,14 @@ bool Controller::statSession::isViewerOn(uint64_t t){ return getUp(t) + getDown(t) > COUNTABLE_BYTES; } -/// Returns true if this session should count as a viewer -bool Controller::statSession::isViewer(){ - long long upTotal = wipedUp + wipedDown; - if (oldConns.size()){ - for (std::deque::iterator it = oldConns.begin(); it != oldConns.end(); ++it){ - if (it->log.size()){ - upTotal += it->log.rbegin()->second.up + it->log.rbegin()->second.down; - if (upTotal > COUNTABLE_BYTES){return true;} - } - } - } - if (curConns.size()){ - for (std::map::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 true if this session should be considered connected +bool Controller::statSession::isConnected(){ + return curConns.size(); +} + +/// Returns true if this session has started (tracked == true) but not yet ended (log entry written) +bool Controller::statSession::isTracked(){ + return tracked; } /// 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. /// 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; - tmp.time = data.time(); - tmp.lastSecond = data.lastSecond(); - tmp.down = data.down(); - tmp.up = data.up(); - log[data.now()] = tmp; + tmp.time = statComm.getTime(index); + tmp.lastSecond = statComm.getLastSecond(index); + tmp.down = statComm.getDown(index); + tmp.up = statComm.getUp(index); + log[statComm.getNow(index)] = tmp; // wipe data older than approx. STAT_CUTOFF seconds /// \todo Remove least interesting data first. if (log.size() > STAT_CUTOFF){log.erase(log.begin());} } -/// This function is called by the shared memory page that holds statistics. -/// It updates the internally saved statistics data, moving across sessions or archiving when necessary. -void Controller::parseStatistics(char *data, size_t len, uint32_t id){ - // retrieve stats data - IPC::statExchange tmpEx(data); +void Controller::statLeadIn(){ + statDropoff = Util::bootSecs() - 3; +} +void Controller::statOnActive(size_t id){ // calculate the current session index, store as idx. - sessIndex idx(tmpEx); - // if the connection was already indexed and it has changed, move it - if (connToSession.count(id) && connToSession[id] != idx){ - if (sessions[connToSession[id]].getSessType() != SESS_UNSET){ - INFO_MSG("Switching connection %" PRIu32 " from active session %s over to %s", id, - connToSession[id].toStr().c_str(), idx.toStr().c_str()); - }else{ - INFO_MSG("Switching connection %" PRIu32 " from inactive session %s over to %s", id, - connToSession[id].toStr().c_str(), idx.toStr().c_str()); + sessIndex idx(statComm, id); + + if (statComm.getNow(id) >= statDropoff){ + // if the connection was already indexed and it has changed, move it + if (connToSession.count(id) && connToSession[id] != idx){ + if (sessions[connToSession[id]].getSessType() != SESS_UNSET){ + INFO_MSG("Switching connection %zu from active session %s over to %s", id, + 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 (!sessions[connToSession[id]].hasData()){sessions.erase(connToSession[id]);} - } - if (!connToSession.count(id)){ - INSANE_MSG("New connection: %" PRIu32 " as %s", id, idx.toStr().c_str()); - } - // store the index for later comparison - 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); + if (!connToSession.count(id)){ + INSANE_MSG("New connection: %zu as %s", id, idx.toStr().c_str()); + } + // store the index for later comparison + connToSession[id] = idx; + // update the session with the latest data + sessions[idx].update(id, statComm); } } +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. bool Controller::hasViewers(std::string streamName){ if (sessions.size()){ - long long currTime = Util::epoch(); + long long currTime = Util::bootSecs(); for (std::map::iterator it = sessions.begin(); it != sessions.end(); it++){ if (it->first.streamName == streamName && (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. bool now = (reqTime == 0); // 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. 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() == "downbps"){fields |= STAT_CLI_BPS_DOWN;} if ((*it).asStringRef() == "upbps"){fields |= STAT_CLI_BPS_UP;} + if ((*it).asStringRef() == "sessid"){fields |= STAT_CLI_SESSID;} } } // 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_UP){rep["fields"].append("upbps");} if (fields & STAT_CLI_CRC){rep["fields"].append("crc");} + if (fields & STAT_CLI_SESSID){rep["fields"].append("sessid");} // output the data itself rep["data"].null(); // 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_UP){d.append(it->second.getBpsUp(time));} if (fields & STAT_CLI_CRC){d.append(it->first.crc);} + if (fields & STAT_CLI_SESSID){d.append(it->first.ID);} rep["data"].append(d); } } @@ -1235,8 +1238,8 @@ void Controller::fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow){ // collect the data first std::set streams; std::map clients; - unsigned int tOut = Util::epoch() - STATS_DELAY; - unsigned int tIn = Util::epoch() - STATS_INPUT_DELAY; + uint64_t tOut = Util::bootSecs() - STATS_DELAY; + uint64_t tIn = Util::bootSecs() - STATS_INPUT_DELAY; // check all sessions { tthread::lock_guard guard(statsMutex); @@ -1263,26 +1266,14 @@ void Controller::fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow){ jsonForEach(req, j){ if (j->asStringRef() == "clients"){rep[*it].append(clients[*it]);} if (j->asStringRef() == "lastms"){ - char pageId[NAME_BUFFER_SIZE]; - IPC::sharedPage streamIndex; - 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(); + DTSC::Meta M(*it, false); + if (M){ uint64_t lms = 0; - DTSC::Scan trcks = strm.getMember("tracks"); - unsigned int trcks_ctr = trcks.getSize(); - for (unsigned int i = 0; i < trcks_ctr; ++i){ - if (trcks.getIndice(i).getMember("lastms").asInt() > lms){ - lms = trcks.getIndice(i).getMember("lastms").asInt(); - } + std::set validTracks = M.getValidTracks(); + for (std::set::iterator jt = validTracks.begin(); jt != validTracks.end(); jt++){ + if (M.getLastms(*jt) > lms){lms = M.getLastms(*jt);} } rep[*it].append(lms); - metaLocker.post(); }else{ 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("end")){reqEnd = req["end"].asInt();} // add the current time, if negative or zero. - if (reqStart < 0){reqStart += Util::epoch();} - if (reqStart == 0){reqStart = Util::epoch() - STAT_CUTOFF;} - if (reqEnd <= 0){reqEnd += Util::epoch();} + if (reqStart < 0){reqStart += Util::bootSecs();} + if (reqStart == 0){reqStart = Util::bootSecs() - STAT_CUTOFF;} + if (reqEnd <= 0){reqEnd += Util::bootSecs();} // at this point, reqStart and reqEnd are the absolute timestamp. 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; if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){ c_total = c_user + c_nice + c_syst + c_idle; - if (c_total - cl_total > 0){ + if (c_total > cl_total){ cpu_use = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total)); }else{ cpu_use = 0; @@ -1556,8 +1547,8 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int // collect the data first std::map outputs; unsigned long totViewers = 0, totInputs = 0, totOutputs = 0; - unsigned int tOut = Util::epoch() - STATS_DELAY; - unsigned int tIn = Util::epoch() - STATS_INPUT_DELAY; + unsigned int tOut = Util::bootSecs() - STATS_DELAY; + unsigned int tIn = Util::bootSecs() - STATS_INPUT_DELAY; // check all sessions if (sessions.size()){ for (std::map::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"; 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=\"down\"}" - << it->second.downBytes << "\n"; + response << "mist_bw{stream=\"" << it->first << "\",direction=\"down\"}" << it->second.downBytes << "\n"; } } H.Chunkify(response.str(), conn); @@ -1657,8 +1647,8 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int // collect the data first std::map outputs; uint64_t totViewers = 0, totInputs = 0, totOutputs = 0; - uint64_t tOut = Util::epoch() - STATS_DELAY; - uint64_t tIn = Util::epoch() - STATS_INPUT_DELAY; + uint64_t tOut = Util::bootSecs() - STATS_DELAY; + uint64_t tIn = Util::bootSecs() - STATS_INPUT_DELAY; // check all sessions if (sessions.size()){ for (std::map::iterator it = sessions.begin(); it != sessions.end(); it++){ diff --git a/src/controller/controller_statistics.h b/src/controller/controller_statistics.h index 7ea06e81..b080cbd6 100644 --- a/src/controller/controller_statistics.h +++ b/src/controller/controller_statistics.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -37,9 +38,8 @@ namespace Controller{ /// Whenever two of these objects are not equal, it will create a new session. class sessIndex{ public: - sessIndex(std::string host, unsigned int crc, std::string streamName, std::string connector); - sessIndex(IPC::statExchange &data); sessIndex(); + sessIndex(const Comms::Statistics &statComm, size_t id); std::string ID; std::string host; unsigned int crc; @@ -57,7 +57,7 @@ namespace Controller{ class statStorage{ public: - void update(IPC::statExchange &data); + void update(Comms::Statistics &statComm, size_t index); bool hasDataFor(unsigned long long); statLog &getDataFor(unsigned long long); std::map log; @@ -87,12 +87,13 @@ namespace Controller{ void wipeOld(uint64_t); void finish(uint64_t index); void switchOverTo(statSession &newSess, uint64_t index); - void update(uint64_t index, IPC::statExchange &data); - void ping(const sessIndex &index, uint64_t disconnectPoint); + void update(uint64_t index, Comms::Statistics &data); + void dropSession(const sessIndex &index); uint64_t getStart(); uint64_t getEnd(); bool isViewerOn(uint64_t time); - bool isViewer(); + bool isConnected(); + bool isTracked(); bool hasDataFor(uint64_t time); bool hasData(); uint64_t getConnTime(uint64_t time); @@ -110,6 +111,7 @@ namespace Controller{ extern std::map sessions; extern std::map connToSession; extern tthread::mutex statsMutex; + extern uint64_t statDropoff; struct triggerLog{ uint64_t totalCount; @@ -119,8 +121,12 @@ namespace Controller{ extern std::map triggerStats; + void statLeadIn(); + void statOnActive(size_t id); + void statOnDisconnect(size_t id); + void statLeadOut(); + std::set 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 fillClients(JSON::Value &req, JSON::Value &rep); void fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow = false); diff --git a/src/controller/controller_storage.cpp b/src/controller/controller_storage.cpp index 6d880e17..e16459a9 100644 --- a/src/controller/controller_storage.cpp +++ b/src/controller/controller_storage.cpp @@ -139,7 +139,11 @@ namespace Controller{ void initState(){ tthread::lock_guard 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){ FAIL_MSG("Could not open memory page for logs buffer"); return; @@ -156,7 +160,11 @@ namespace Controller{ } 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){ FAIL_MSG("Could not open memory page for access logs buffer"); return; @@ -176,7 +184,11 @@ namespace Controller{ } 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){ FAIL_MSG("Could not open memory page for stream data"); return; diff --git a/src/controller/controller_streams.cpp b/src/controller/controller_streams.cpp index 32628b96..6e699aed 100644 --- a/src/controller/controller_streams.cpp +++ b/src/controller/controller_streams.cpp @@ -295,8 +295,8 @@ namespace Controller{ Util::sanitizeName(cleaned); std::string strmSource; if (Util::getStreamStatus(cleaned) != STRMSTAT_OFF){ - DTSC::Meta mData = Util::getStreamMeta(cleaned); - if (mData.sourceURI.size()){strmSource = mData.sourceURI;} + DTSC::Meta M(cleaned, false); + if (M && M.getSource().size()){strmSource = M.getSource();} } if (!strmSource.size()){ std::string smp = cleaned.substr(0, cleaned.find_first_of("+ ")); diff --git a/src/controller/controller_updater.cpp b/src/controller/controller_updater.cpp index 02c4c23c..8075b866 100644 --- a/src/controller/controller_updater.cpp +++ b/src/controller/controller_updater.cpp @@ -24,30 +24,6 @@ #define SHARED_SECRET "empty" #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; uint8_t updatePerc = 0; JSON::Value updates; diff --git a/src/input/input.cpp b/src/input/input.cpp index d760c479..a32a9866 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -4,8 +4,12 @@ #include "input.h" #include +#include #include +#include #include +#include +#include #include #include #include @@ -13,23 +17,30 @@ #include namespace Mist{ - Input *Input::singleton = NULL; Util::Config *Input::config = NULL; - void Input::userCallback(char *data, size_t len, unsigned int id){ - for (int i = 0; i < SIMUL_TRACKS; i++){ - unsigned long tid = ((unsigned long)(data[i * 6]) << 24) | ((unsigned long)(data[i * 6 + 1]) << 16) | - ((unsigned long)(data[i * 6 + 2]) << 8) | ((unsigned long)(data[i * 6 + 3])); - if (tid){ - unsigned long keyNum = ((unsigned long)(data[i * 6 + 4]) << 8) | ((unsigned long)(data[i * 6 + 5])); - bufferFrame(tid, keyNum + 1); // Try buffer next frame - } + void Input::userLeadIn(){connectedUsers = 0;} + void Input::userOnActive(size_t id){ + ++connectedUsers; + size_t track = users.getTrack(id); + size_t key = users.getKeyNum(id); + uint64_t time = M.getTimeForKeyIndex(track, key); + size_t endKey = M.getKeyIndexForTime(track, time + 20000); + for (size_t i = key; i <= endKey; i++){bufferFrame(track, i);} + } + void Input::userOnDisconnect(size_t id){} + void Input::userLeadOut(){} + + void Input::reloadClientMeta(){ + if (M.getStreamName() != "" && M.getMaster()){return;} + if (M.getStreamName() != streamName){ + meta.reInit(streamName, false); + }else{ + meta.refresh(); } } - void Input::callbackWrapper(char *data, size_t len, unsigned int id){ - singleton->userCallback(data, 30, id); // call the userCallback for this input - } + bool Input::hasMeta() const{return M && M.getStreamName() != "" && M.getValidTracks().size();} Input::Input(Util::Config *cfg) : InOutBase(){ config = cfg; @@ -59,19 +70,22 @@ namespace Mist{ option["long"] = "stream"; option["help"] = "The name of the stream that this connector will provide in player mode"; config->addOption("streamname", option); + option.null(); + option["short"] = "H"; + option["long"] = "headerOnly"; + option["value"].append(0u); + option["help"] = "Generate .dtsh, then exit"; + config->addOption("headeronly", option); - /*LTS-START*/ - // Encryption + /*LTS-START + //Encryption + option.null(); option["arg"] = "string"; - option["long"] = "verimatrix-playready"; - option["short"] = "P"; - option["help"] = "URL of the Verimatrix PlayReady keyserver"; - config->addOption("verimatrix-playready", option); - capa["optional"]["verimatrix-playready"]["name"] = "Verimatrix PlayReady Server"; - capa["optional"]["verimatrix-playready"]["help"] = "URL of the Verimatrix PlayReady keyserver"; - capa["optional"]["verimatrix-playready"]["option"] = "--verimatrix-playready"; - capa["optional"]["verimatrix-playready"]["type"] = "str"; - capa["optional"]["verimatrix-playready"]["default"] = ""; + option["short"] = "e"; + option["long"] = "encryption"; + option["help"] = "a KID:KEY combo for auto-encrypting tracks"; + config->addOption("encryption", option); + option.null(); option["long"] = "realtime"; @@ -97,20 +111,77 @@ namespace Mist{ capa["optional"]["simulated-starttime"]["type"] = "uint"; capa["optional"]["simulated-starttime"]["default"] = 0; - /*LTS-END*/ + option.null(); + option["arg"] = "string"; + option["short"] = "B"; + option["long"] = "buydrm"; + option["help"] = "Your BuyDRM user string"; + config->addOption("buydrm", option); + + option.null(); + option["arg"] = "string"; + option["short"] = "C"; + option["long"] = "contentid"; + option["help"] = "The content ID for this stream, defaults to an md5 hash of the stream name"; + config->addOption("contentid", option); + + option.null(); + option["arg"] = "string"; + option["short"] = "K"; + option["long"] = "keyid"; + option["help"] = "The Key ID for this stream, defaults to an md5 hash of the content key"; + config->addOption("keyid", option); + + option.null(); + option["arg"] = "string"; + option["short"] = "W"; + option["long"] = "widevine"; + option["help"] = "The header to use for Widevine encryption."; + config->addOption("widevine", option); + + option.null(); + option["arg"] = "string"; + option["short"] = "P"; + option["long"] = "playready"; + option["help"] = "The header to use for PlayReady encryption"; + config->addOption("playready", option); + + capa["optional"]["encryption"]["name"] = "encryption"; + capa["optional"]["encryption"]["help"] = "Encryption key."; + capa["optional"]["encryption"]["option"] = "--encryption"; + capa["optional"]["encryption"]["type"] = "string"; + + capa["optional"]["buydrm"]["name"] = "buydrm"; + capa["optional"]["buydrm"]["help"] = "BuyDRM User Key."; + capa["optional"]["buydrm"]["option"] = "--buydrm"; + capa["optional"]["buydrm"]["type"] = "string"; + + capa["optional"]["contentid"]["name"] = "contentid"; + capa["optional"]["contentid"]["help"] = "The content ID to use for this stream's encryption. + Defaults to an md5 hash of the stream name."; capa["optional"]["contentid"]["option"] = + "--contentid"; capa["optional"]["contentid"]["type"] = "string"; + + capa["optional"]["keyid"]["name"] = "keyid"; + capa["optional"]["keyid"]["help"] = "The Key ID to use for this stream's encryption. Defaults to + an md5 hash of the content key."; capa["optional"]["keyid"]["option"] = "--keyid"; + capa["optional"]["keyid"]["type"] = "string"; + + capa["optional"]["widevine"]["name"] = "widevine"; + capa["optional"]["widevine"]["help"] = "The header to use for Widevine encryption."; + capa["optional"]["widevine"]["option"] = "--widevine"; + capa["optional"]["widevine"]["type"] = "string"; + + capa["optional"]["playready"]["name"] = "playready"; + capa["optional"]["playready"]["help"] = "The header to use for PlayReady encryption."; + capa["optional"]["playready"]["option"] = "--playready"; + capa["optional"]["playready"]["type"] = "string"; + LTS-END*/ + capa["optional"]["debug"]["name"] = "debug"; capa["optional"]["debug"]["help"] = "The debug level at which messages need to be printed."; capa["optional"]["debug"]["option"] = "--debug"; capa["optional"]["debug"]["type"] = "debug"; - packTime = 0; - lastActive = Util::epoch(); - playing = 0; - playUntil = 0; - - singleton = this; - isBuffer = false; - hasSrt = false; srtTrack = 0; } @@ -152,15 +223,15 @@ namespace Mist{ void Input::readSrtHeader(){ if (!hasSrt){return;} if (!srtSource.good()){return;} - srtTrack = myMeta.tracks.rbegin()->first + 1; - myMeta.tracks[srtTrack].trackID = srtTrack; - myMeta.tracks[srtTrack].type = "meta"; - myMeta.tracks[srtTrack].codec = "subtitle"; + srtTrack = meta.addTrack(); + meta.setID(srtTrack, srtTrack); + meta.setType(srtTrack, "meta"); + meta.setCodec(srtTrack, "subtitle"); getNextSrt(); while (srtPack){ - myMeta.update(srtPack); + meta.update(srtPack); getNextSrt(); } srtSource.clear(); @@ -168,8 +239,6 @@ namespace Mist{ } void Input::getNextSrt(bool smart){ - bool hasPacket = false; - srtPack.null(); std::string line; @@ -188,7 +257,7 @@ namespace Mist{ static JSON::Value thisPack; thisPack.null(); thisPack["trackid"] = srtTrack; - thisPack["bpos"] = (uint64_t)srtSource.tellg(); + thisPack["bpos"] = srtSource.tellg(); thisPack["data"] = data; thisPack["index"] = index; thisPack["time"] = timestamp; @@ -197,41 +266,41 @@ namespace Mist{ std::string tmpStr = thisPack.toNetPacked(); srtPack.reInit(tmpStr.data(), tmpStr.size()); return; + } + // INFO_MSG("printline size: %d, string: %s", line.size(), line.c_str()); + if (lineNr == 1){ + index = atoi(line.c_str()); + }else if (lineNr == 2){ + // timestamp + int from_hour = 0; + int from_min = 0; + int from_sec = 0; + int from_ms = 0; + + int to_hour = 0; + int to_min = 0; + int to_sec = 0; + int to_ms = 0; + sscanf(line.c_str(), "%d:%d:%d,%d --> %d:%d:%d,%d", &from_hour, &from_min, &from_sec, + &from_ms, &to_hour, &to_min, &to_sec, &to_ms); + + timestamp = (from_hour * 60 * 60 * 1000) + (from_min * 60 * 1000) + (from_sec * 1000) + from_ms; + duration = ((to_hour * 60 * 60 * 1000) + (to_min * 60 * 1000) + (to_sec * 1000) + to_ms) - timestamp; }else{ - // INFO_MSG("printline size: %d, string: %s", line.size(), line.c_str()); - if (lineNr == 1){ - index = atoi(line.c_str()); - }else if (lineNr == 2){ - // timestamp - int from_hour = 0; - int from_min = 0; - int from_sec = 0; - int from_ms = 0; - - int to_hour = 0; - int to_min = 0; - int to_sec = 0; - int to_ms = 0; - sscanf(line.c_str(), "%d:%d:%d,%d --> %d:%d:%d,%d", &from_hour, &from_min, &from_sec, - &from_ms, &to_hour, &to_min, &to_sec, &to_ms); - - timestamp = (from_hour * 60 * 60 * 1000) + (from_min * 60 * 1000) + (from_sec * 1000) + from_ms; - duration = ((to_hour * 60 * 60 * 1000) + (to_min * 60 * 1000) + (to_sec * 1000) + to_ms) - timestamp; - }else{ - // subtitle - if (data.size() > 1){data.append("\n");} - data.append(line); - } + // subtitle + if (data.size() > 1){data.append("\n");} + data.append(line); } } - if (!srtSource.eof()){FAIL_MSG("Could not get next subtitle packet");} + srtPack.null(); + FAIL_MSG("Could not get next srt packet!"); } /// Starts checks the SEM_INPUT lock, starts an angel process and then int Input::boot(int argc, char *argv[]){ if (!(config->parseArgs(argc, argv))){return 1;} - streamName = nProxy.streamName = config->getString("streamname"); + streamName = config->getString("streamname"); Util::Config::streamName = streamName; if (config->getBool("json")){ @@ -260,7 +329,7 @@ namespace Mist{ snprintf(semName, NAME_BUFFER_SIZE, SEM_INPUT, streamName.c_str()); playerLock.open(semName, O_CREAT | O_RDWR, ACCESSPERMS, 1); if (!playerLock.tryWait()){ - INFO_MSG("A player for stream %s is already running", streamName.c_str()); + INFO_MSG("A player for this stream is already running"); playerLock.close(); return 1; } @@ -279,11 +348,12 @@ namespace Mist{ if (isSingular()){ pullLock.open(std::string("/MstPull_" + streamName).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1); if (!pullLock){ - FAIL_MSG("Could not open pull lock for stream '%s' - aborting!", streamName.c_str()); + FAIL_MSG("Could not open pull lock - aborting!"); return 1; } - if (!pullLock.tryWait()){ - WARN_MSG("A pull process for stream %s is already running", streamName.c_str()); + // We wait at most 5 seconds for a lock + if (!pullLock.tryWait(5000)){ + WARN_MSG("A pull process for this stream is already running"); pullLock.close(); return 1; } @@ -317,7 +387,10 @@ namespace Mist{ return ret; } +#if DEBUG < DLVL_DEVEL uint64_t reTimer = 0; +#endif + while (config->is_active){ Util::Procs::fork_prepare(); pid_t pid = fork(); @@ -395,23 +468,23 @@ namespace Mist{ } int Input::run(){ - myMeta.sourceURI = config->getString("input"); if (streamStatus){streamStatus.mapped[0] = STRMSTAT_BOOT;} checkHeaderTimes(config->getString("input")); if (needHeader()){ uint64_t timer = Util::bootMS(); bool headerSuccess = readHeader(); - if (!headerSuccess){ - std::cerr << "Reading header for " << config->getString("input") << " failed." << std::endl; + if (!headerSuccess || (!M && needsLock())){ + FAIL_MSG("Reading header for '%s' failed.", config->getString("input").c_str()); return 0; - }else{ - timer = Util::bootMS() - timer; - DEBUG_MSG(DLVL_DEVEL, "Read header for '%s' in %" PRIu64 "ms", streamName.c_str(), timer); } + timer = Util::bootMS() - timer; + INFO_MSG("Read header in %" PRIu64 "ms (%zu tracks)", timer, M.trackCount()); } - if (myMeta.vod){ + if (config->getBool("headeronly")){return 0;} + if (M && M.getVod()){ + meta.removeEmptyTracks(); parseHeader(); - MEDIUM_MSG("Header parsed, %lu tracks", myMeta.tracks.size()); + INFO_MSG("Header parsed, %lu tracks", M.getValidTracks().size()); } if (!streamName.size()){ @@ -419,46 +492,67 @@ namespace Mist{ MEDIUM_MSG("Starting convert"); convert(); }else if (!needsLock()){ - // We have a name and aren't the sole process. That means we're streaming live data to a buffer. - MEDIUM_MSG("Starting stream"); + // We have a name and aren't the sole process. That means we're streaming live data to a + // buffer. + INFO_MSG("Starting stream"); stream(); }else{ // We are the sole process and have a name. That means this is a Buffer or VoD input. - MEDIUM_MSG("Starting serve"); + INFO_MSG("Starting serve"); serve(); } return 0; } void Input::convert(){ - // check filename for no - - if (config->getString("output") != "-"){ - std::string filename = config->getString("output"); - if (filename.size() < 5 || filename.substr(filename.size() - 5) != ".dtsc"){ - filename += ".dtsc"; - } - // output to dtsc - DTSC::Meta newMeta = myMeta; - newMeta.reset(); - std::ofstream file(filename.c_str()); - long long int bpos = 0; - seek(0); - getNext(); - while (thisPacket){ - newMeta.updatePosOverride(thisPacket, bpos); - file.write(thisPacket.getData(), thisPacket.getDataLen()); - bpos += thisPacket.getDataLen(); - getNext(); - } - // close file - file.close(); - // create header - file.open((filename + ".dtsh").c_str()); - file << newMeta.toJSON().toNetPacked(); - file.close(); - }else{ - DEBUG_MSG(DLVL_FAIL, "No filename specified, exiting"); + INFO_MSG("Starting conversion"); + std::string fileName = config->getString("output"); + if (fileName == "-"){ + FAIL_MSG("No filename specified, exiting"); + return; } + if (fileName.size() < 5 || fileName.substr(fileName.size() - 5) != ".dtsc"){ + fileName += ".dtsc"; + } + std::ofstream file(fileName.c_str()); + + DTSC::Meta outMeta; + + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + std::string type = M.getType(*it); + size_t idx = outMeta.addTrack(M.fragments(*it).getPresent(), M.keys(*it).getPresent(), + M.parts(*it).getPresent()); + outMeta.setID(idx, M.getID(*it)); + outMeta.setType(idx, type); + outMeta.setCodec(idx, M.getCodec(*it)); + outMeta.setInit(idx, M.getInit(*it)); + outMeta.setLang(idx, M.getLang(*it)); + if (type == "video"){ + outMeta.setHeight(idx, M.getHeight(*it)); + outMeta.setWidth(idx, M.getWidth(*it)); + outMeta.setFpks(idx, M.getFpks(*it)); + } + if (type == "audio"){ + outMeta.setRate(idx, M.getRate(*it)); + outMeta.setSize(idx, M.getSize(*it)); + outMeta.setChannels(idx, M.getChannels(*it)); + } + } + // output to dtsc + uint64_t bpos = 0; + + seek(0); + getNext(); + while (thisPacket){ + outMeta.updatePosOverride(thisPacket, bpos); + file.write(thisPacket.getData(), thisPacket.getDataLen()); + bpos += thisPacket.getDataLen(); + getNext(); + } + // close file + file.close(); + outMeta.toFile(fileName + ".dtsh"); } /// Checks in the server configuration if this stream is set to always on or not. @@ -494,15 +588,21 @@ namespace Mist{ /// input name /// ~~~~~~~~~~~~~~~ void Input::serve(){ - if (!isBuffer){ - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - bufferFrame(it->first, 1); + users.reload(streamName, true); + + if (!M){ + // Initialize meta page + meta.reInit(streamName, true); + }else{ + std::set validTracks = M.getValidTracks(true); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + bufferFrame(*it, 0); } } - char userPageName[NAME_BUFFER_SIZE]; - snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); - userPage.init(userPageName, PLAY_EX_SIZE, true); + meta.setSource(config->getString("input")); + + bool internalOnly = (config->getString("input").find("INTERNAL_ONLY") != std::string::npos); + /*LTS-START*/ if (Triggers::shouldTrigger("STREAM_READY", config->getString("streamname"))){ std::string payload = config->getString("streamname") + "\n" + capa["name"].asStringRef(); @@ -513,22 +613,24 @@ namespace Mist{ /*LTS-END*/ if (streamStatus){streamStatus.mapped[0] = STRMSTAT_READY;} - INFO_MSG("Input for stream %s started", streamName.c_str()); + INFO_MSG("Input started"); activityCounter = Util::bootSecs(); // main serve loop while (keepRunning()){ // load pages for connected clients on request - // through the callbackWrapper function - userPage.parseEach(callbackWrapper); + userLeadIn(); + COMM_LOOP(users, userOnActive(id), userOnDisconnect(id)) + userLeadOut(); + // unload pages that haven't been used for a while removeUnused(); - // If users are connected and tracks exist, reset the activity counter - // Also reset periodically if the stream is configured as Always on - if (userPage.connectedUsers || - ((Util::bootSecs() - activityCounter) > INPUT_TIMEOUT / 2 && isAlwaysOn())){ - if (myMeta.tracks.size()){activityCounter = Util::bootSecs();} + + if (M.getLive() && !internalOnly){ + uint64_t currLastUpdate = M.getLastUpdated(); + if (currLastUpdate > activityCounter){activityCounter = currLastUpdate;} + }else{ + if (connectedUsers && M.getValidTracks().size()){activityCounter = Util::bootSecs();} } - INSANE_MSG("Connected: %d users, %d total", userPage.connectedUsers, userPage.amount); // if not shutting down, wait 1 second before looping if (config->is_active){Util::wait(INPUT_USER_INTERVAL);} } @@ -536,9 +638,8 @@ namespace Mist{ config->is_active = false; finish(); INFO_MSG("Input for stream %s closing clean", streamName.c_str()); - userPage.finishEach(); + userSelect.clear(); if (streamStatus){streamStatus.mapped[0] = STRMSTAT_OFF;} - // end player functionality } /// This function checks if an input in serve mode should keep running or not. @@ -551,9 +652,7 @@ namespace Mist{ // We keep running in serve mode if the config is still active AND either // - INPUT_TIMEOUT seconds haven't passed yet, // - this is a live stream and at least two of the biggest fragment haven't passed yet, - bool ret = (config->is_active && - ((Util::bootSecs() - activityCounter) < INPUT_TIMEOUT || - (myMeta.live && (Util::bootSecs() - activityCounter) < myMeta.biggestFragment() / 500))); + bool ret = config->is_active && ((Util::bootSecs() - activityCounter) < INPUT_TIMEOUT); if (!ret && config->is_active && isAlwaysOn()){ ret = true; activityCounter = Util::bootSecs(); @@ -607,27 +706,23 @@ namespace Mist{ return; } + INFO_MSG("Input started"); + meta.reInit(streamName, false); + if (!openStreamSource()){ FAIL_MSG("Unable to connect to source"); return; } - - char userPageName[NAME_BUFFER_SIZE]; - snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); - nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); - nProxy.userClient.countAsViewer = false; - parseStreamHeader(); - if (myMeta.tracks.size() == 0){ - nProxy.userClient.finish(); + std::set validTracks = M.getMySourceTracks(getpid()); + if (!validTracks.size()){ + userSelect.clear(); finish(); INFO_MSG("No tracks found, cancelling"); return; } - nProxy.pagesByTrack.clear(); - timeOffset = 0; uint64_t minFirstMs = 0; @@ -660,58 +755,6 @@ namespace Mist{ "ms - %" PRIu64 "ms)", maxLastMs - minLastMs, minLastMs, maxLastMs); } - - bool needsWait = true; - // change firstms of all tracks to -1 to indicate we're resuming - std::map preFirstMs; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); ++it){ - preFirstMs[it->first] = it->second.firstms; - it->second.firstms = -1; - } - // negotiate all tracks with buffer - while (needsWait && config->is_active && nProxy.userClient.isAlive()){ - needsWait = false; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); ++it){ - if (!it->first){continue;} - if (nProxy.trackState.count(it->first) && nProxy.trackState[it->first] == FILL_ACC){ - continue; - } - nProxy.continueNegotiate(it->first, myMeta); - if (nProxy.trackState.count(it->first) && nProxy.trackState[it->first] == FILL_ACC){ - continue; - } - needsWait = true; - } - if (needsWait){Util::sleep(200);} - } - // Restore firstms of tracks - should not be needed, but why risk messing things up..? - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); ++it){ - it->second.firstms = preFirstMs[it->first]; - } - - char nameBuf[NAME_BUFFER_SIZE]; - snprintf(nameBuf, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); - IPC::sharedPage curMeta(nameBuf); - - static char liveSemName[NAME_BUFFER_SIZE]; - snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str()); - IPC::semaphore *liveSem = new IPC::semaphore(liveSemName, O_RDWR, ACCESSPERMS, 8, !myMeta.live); - if (*liveSem){ - liveSem->wait(); - }else{ - delete liveSem; - liveSem = 0; - } - DTSC::Packet tmpMeta(curMeta.mapped, curMeta.len, true); - if (liveSem){ - liveSem->post(); - delete liveSem; - liveSem = 0; - } - DTSC::Meta tmpM(tmpMeta); // find highest current time for (std::map::iterator secondIt = tmpM.tracks.begin(); secondIt != tmpM.tracks.end(); ++secondIt){ @@ -749,18 +792,53 @@ namespace Mist{ closeStreamSource(); - nProxy.userClient.finish(); + userSelect.clear(); + finish(); - INFO_MSG("Stream input %s closing clean; reason: %s", streamName.c_str(), reason.c_str()); + INFO_MSG("Input closing clean; reason: %s", reason.c_str()); return; } std::string Input::streamMainLoop(){ + uint64_t statTimer = 0; + uint64_t startTime = Util::bootSecs(); + Comms::Statistics statComm; getNext(); - while (thisPacket && config->is_active && nProxy.userClient.isAlive()){ - nProxy.bufferLivePacket(thisPacket, myMeta); + if (thisPacket && !userSelect.count(thisPacket.getTrackId())){ + size_t tid = thisPacket.getTrackId(); + userSelect[tid].reload(streamName, tid, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK); + } + while (thisPacket && config->is_active && userSelect[thisPacket.getTrackId()].isAlive()){ + bufferLivePacket(thisPacket); + userSelect[thisPacket.getTrackId()].keepAlive(); getNext(); - nProxy.userClient.keepAlive(); + if (thisPacket && !userSelect.count(thisPacket.getTrackId())){ + size_t tid = thisPacket.getTrackId(); + userSelect[tid].reload(streamName, tid, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK); + } + + if (Util::bootSecs() - statTimer > 1){ + // Connect to stats for INPUT detection + if (!statComm){statComm.reload();} + if (statComm){ + if (!statComm.isAlive()){ + config->is_active = false; + return "received shutdown request from controller"; + } + uint64_t now = Util::bootSecs(); + statComm.setNow(now); + statComm.setCRC(getpid()); + statComm.setStream(streamName); + statComm.setConnector("INPUT"); + statComm.setUp(0); + statComm.setDown(streamByteCount()); + statComm.setTime(now - startTime); + statComm.setLastSecond(0); + statComm.keepAlive(); + } + + statTimer = Util::bootSecs(); + } } if (!nProxy.userClient.isAlive()){return "buffer shutdown";} if (!config->is_active){return "received deactivate signal";} @@ -787,281 +865,331 @@ namespace Mist{ } if (!thisPacket){return "end of file";} if (!config->is_active){return "received deactivate signal";} - if (!nProxy.userClient.isAlive()){return "buffer shutdown";} + if (!userSelect[thisPacket.getTrackId()].isAlive()){return "buffer shutdown";} return "Unknown"; } void Input::finish(){ if (!standAlone || config->getBool("realtime")){return;} - for (std::map >::iterator it = pageCounter.begin(); + for (std::map >::iterator it = pageCounter.begin(); it != pageCounter.end(); it++){ - for (std::map::iterator it2 = it->second.begin(); - it2 != it->second.end(); it2++){ + for (std::map::iterator it2 = it->second.begin(); it2 != it->second.end(); it2++){ it2->second = 1; } } removeUnused(); - for (std::map::iterator it = nProxy.metaPages.begin(); - it != nProxy.metaPages.end(); it++){ - it->second.master = true; - } } void Input::removeUnused(){ - for (std::map >::iterator it = pageCounter.begin(); - it != pageCounter.end(); it++){ - for (std::map::iterator it2 = it->second.begin(); - it2 != it->second.end(); it2++){ - it2->second--; - } - bool change = true; - while (change){ - change = false; - for (std::map::iterator it2 = it->second.begin(); - it2 != it->second.end(); it2++){ - if (!it2->second){ - bufferRemove(it->first, it2->first); - pageCounter[it->first].erase(it2->first); - change = true; - break; + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); ++it){ + Util::RelAccX &tPages = meta.pages(*it); + for (size_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ + uint64_t pageNum = tPages.getInt("firstkey", i); + if (pageCounter[*it].count(pageNum)){ + --pageCounter[*it][pageNum]; + if (!pageCounter[*it][pageNum]){ + pageCounter[*it].erase(pageNum); + bufferRemove(*it, pageNum); } } } } } - void Input::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 = ""; - } - } + std::string formatGUID(const std::string &val){ + std::stringstream r; + r << std::hex << std::setw(2) << std::setfill('0'); + r << (int)val[0] << (int)val[1] << (int)val[2] << (int)val[3]; + r << "-"; + r << (int)val[4] << (int)val[5]; + r << "-"; + r << (int)val[6] << (int)val[7]; + r << "-"; + r << (int)val[8] << (int)val[9]; + r << "-"; + r << (int)val[10] << (int)val[11] << (int)val[12] << (int)val[13] << (int)val[14] << (int)val[15]; + return r.str(); + } + + bool Input::readHeader(){ + INFO_MSG("Empty header created by default readHeader handler"); + meta.reInit(streamName); + return true; } void Input::parseHeader(){ if (hasSrt){readSrtHeader();} - DEBUG_MSG(DLVL_DONTEVEN, "Parsing the header"); - selectedTracks.clear(); - std::stringstream trackSpec; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - DEBUG_MSG(DLVL_VERYHIGH, "Track %u encountered", it->first); - if (trackSpec.str() != ""){trackSpec << " ";} - trackSpec << it->first; - DEBUG_MSG(DLVL_VERYHIGH, "Trackspec now %s", trackSpec.str().c_str()); - for (std::deque::iterator it2 = it->second.keys.begin(); it2 != it->second.keys.end(); it2++){ - keyTimes[it->first].insert(it2->getTime()); - } - } - trackSelect(trackSpec.str()); - + DONTEVEN_MSG("Parsing the header"); bool hasKeySizes = true; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (!it->second.keySizes.size()){ - hasKeySizes = false; - break; + + std::set validTracks = M.getValidTracks(true); + + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); ++it){ + DTSC::Keys keys(M.keys(*it)); + size_t endKey = keys.getEndValid(); + INFO_MSG("Track %zu has %zu keys", *it, endKey); + std::set &kTimes = keyTimes[*it]; + for (size_t j = 0; j < endKey; j++){ + kTimes.insert(keys.getTime(j)); + if (hasKeySizes && keys.getSize(j) == 0){hasKeySizes = false;} } } - if (hasKeySizes){ - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - bool newData = true; - for (int i = 0; i < it->second.keys.size(); i++){ + + if (!hasKeySizes){ + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); ++it){ + DTSC::Keys keys(M.keys(*it)); + DTSC::Parts parts(M.parts(*it)); + size_t partIndex = 0; + size_t keyCount = keys.getEndValid(); + for (size_t i = 0; i < keyCount; ++i){ + size_t keySize = 0; + size_t partCount = keys.getParts(i); + for (size_t j = 0; j < partCount; ++j){keySize += parts.getSize(partIndex + j);} + keys.setSize(i, keySize); + partIndex += partCount; + } + } + } + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); ++it){ + bool newData = true; + + DTSC::Keys keys(M.keys(*it)); + uint32_t endKey = keys.getEndValid(); + + Util::RelAccX &tPages = meta.pages(*it); + // Generate page data only if not set yet (might be crash-recovering here) + if (!tPages.getEndPos()){ + int32_t pageNum = -1; + for (uint32_t j = 0; j < endKey; j++){ + uint64_t keyTime = keys.getTime(j); if (newData){ - // i+1 because keys are 1-indexed - nProxy.pagesByTrack[it->first][i + 1].firstTime = it->second.keys[i].getTime(); + tPages.addRecords(1); + ++pageNum; + tPages.setInt("firsttime", keyTime, pageNum); + tPages.setInt("firstkey", j, pageNum); + newData = false; } - DTSCPageData &dPage = nProxy.pagesByTrack[it->first].rbegin()->second; - dPage.keyNum++; - if (it->second.keys.size() <= i || it->second.keySizes.size() <= i){ - FAIL_MSG("Corrupt header - deleting for regeneration and aborting"); - std::string headerFile = config->getString("input"); - headerFile += ".dtsh"; - remove(headerFile.c_str()); - return; - } - dPage.partNum += it->second.keys[i].getParts(); - dPage.dataSize += it->second.keySizes[i]; - if ((dPage.dataSize > FLIP_DATA_PAGE_SIZE || - it->second.keys[i].getTime() - dPage.firstTime > FLIP_TARGET_DURATION) && - it->second.keys[i].getTime() - dPage.firstTime > FLIP_MIN_DURATION){ + tPages.setInt("keycount", tPages.getInt("keycount", pageNum) + 1, pageNum); + tPages.setInt("parts", tPages.getInt("parts", pageNum) + keys.getParts(j), pageNum); + tPages.setInt("size", tPages.getInt("size", pageNum) + keys.getSize(j), pageNum); + tPages.setInt("lastkeytime", keyTime, pageNum); + if ((tPages.getInt("size", pageNum) > FLIP_DATA_PAGE_SIZE || + keyTime - tPages.getInt("firsttime", pageNum) > FLIP_TARGET_DURATION) && + keyTime - tPages.getInt("firsttime", pageNum) > FLIP_MIN_DURATION){ newData = true; } } } - }else{ - std::map curData; - std::map bookKeeping; - - seek(0); - getNext(); - - while (thisPacket){// loop through all - unsigned int tid = thisPacket.getTrackId(); - if (!tid){ - getNext(false); - continue; - } - if (!bookKeeping.count(tid)){ - bookKeeping[tid].first = 1; - bookKeeping[tid].curPart = 0; - bookKeeping[tid].curKey = 0; - - curData[tid].lastKeyTime = 0xFFFFFFFF; - curData[tid].keyNum = 1; - curData[tid].partNum = 0; - curData[tid].dataSize = 0; - curData[tid].curOffset = 0; - curData[tid].firstTime = myMeta.tracks[tid].keys[0].getTime(); - } - if (myMeta.tracks[tid].keys.size() <= bookKeeping[tid].curKey){ - FAIL_MSG("Corrupt header - deleting for regeneration and aborting"); - std::string headerFile = config->getString("input"); - headerFile += ".dtsh"; - remove(headerFile.c_str()); - return; - } - if (myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getParts() + 1 == curData[tid].partNum){ - if ((curData[tid].dataSize > FLIP_DATA_PAGE_SIZE || - myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getTime() - curData[tid].firstTime > FLIP_TARGET_DURATION) && - myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getTime() - curData[tid].firstTime > FLIP_MIN_DURATION){ - nProxy.pagesByTrack[tid][bookKeeping[tid].first] = curData[tid]; - bookKeeping[tid].first += curData[tid].keyNum; - curData[tid].keyNum = 0; - curData[tid].dataSize = 0; - curData[tid].firstTime = myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getTime(); - } - bookKeeping[tid].curKey++; - curData[tid].keyNum++; - curData[tid].partNum = 0; - } - curData[tid].dataSize += thisPacket.getDataLen(); - curData[tid].partNum++; - bookKeeping[tid].curPart++; - DEBUG_MSG(DLVL_DONTEVEN, "Track %ld:%llu on page %d@%llu (len:%d), being part %lu of key %lu", - thisPacket.getTrackId(), thisPacket.getTime(), bookKeeping[tid].first, - curData[tid].dataSize, thisPacket.getDataLen(), curData[tid].partNum, - bookKeeping[tid].first + curData[tid].keyNum); - getNext(false); + /*LTS-START*/ + /* + if (config->getString("encryption") == "" && config->getString("buydrm") != ""){ + handleBuyDRM(); } - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (curData.count(it->first) && !nProxy.pagesByTrack[it->first].count(bookKeeping[it->first].first)){ - nProxy.pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->first]; - } + if (config->getString("encryption").find(":") != std::string::npos){ + size_t tNumber = meta.addCopy(*it); + meta.setID(tNumber, tNumber); + meta.setType(tNumber, M.getType(*it)); + meta.setCodec(tNumber, M.getCodec(*it)); + meta.setEncryption(tNumber, "CTR128/" + config->getString("encryption")); + meta.setIvec(tNumber, 0x0CD00C657BA88D47);//Poke_compat +// meta.setIvec(tNumber, 0x5DC800E53A65018A);//Dash_compat + meta.setWidevine(tNumber, config->getString("widevine")); + meta.setPlayReady(tNumber, config->getString("playready")); + + tNumber = meta.addCopy(*it); + meta.setID(tNumber, tNumber); + meta.setType(tNumber, M.getType(*it)); + meta.setCodec(tNumber, M.getCodec(*it)); + meta.setEncryption(tNumber, "CBC128/" + config->getString("encryption")); + meta.setIvec(tNumber, 0x0CD00C657BA88D47);//Poke_compat +// meta.setIvec(tNumber, 0x5DC800E53A65018A);//Dash_compat + meta.setWidevine(tNumber, config->getString("widevine")); + meta.setPlayReady(tNumber, config->getString("playready")); } + */ + /*LTS-END*/ } - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (!nProxy.pagesByTrack.count(it->first)){ - DEBUG_MSG(DLVL_WARN, "No pages for track %d found", it->first); - }else{ - DEBUG_MSG(DLVL_MEDIUM, "Track %d (%s) split into %lu pages", it->first, - myMeta.tracks[it->first].codec.c_str(), nProxy.pagesByTrack[it->first].size()); - for (std::map::iterator it2 = nProxy.pagesByTrack[it->first].begin(); - it2 != nProxy.pagesByTrack[it->first].end(); it2++){ - DEBUG_MSG(DLVL_VERYHIGH, "Page %lu-%lu, (%llu bytes)", it2->first, - it2->first + it2->second.keyNum - 1, it2->second.dataSize); - } + + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); ++it){ + const Util::RelAccX &tPages = meta.pages(*it); + if (!tPages.getEndPos()){ + WARN_MSG("No pages for track %zu found", *it); + continue; + } + MEDIUM_MSG("Track %zu (%s) split into %zu pages", *it, M.getCodec(*it).c_str(), tPages.getEndPos()); + for (size_t j = tPages.getDeleted(); j < tPages.getEndPos(); j++){ + size_t pageNumber = tPages.getInt("firstkey", j); + size_t pageKeys = tPages.getInt("keycount", j); + size_t pageSize = tPages.getInt("size", j); + + HIGH_MSG(" Page %zu-%zu, (%zu bytes)", pageNumber, pageNumber + pageKeys - 1, pageSize); } } } - bool Input::bufferFrame(unsigned int track, unsigned int keyNum){ - VERYHIGH_MSG("Buffering stream %s, track %u, key %u", streamName.c_str(), track, keyNum); - if (keyNum > myMeta.tracks[track].keys.size()){ - // End of movie here, returning true to avoid various error messages - WARN_MSG("Key %u is higher than total (%zu). Cancelling buffering.", keyNum, - myMeta.tracks[track].keys.size()); - return true; + void getCData(std::string &val){ + if (val.find("::iterator it = nProxy.pagesByTrack[track].begin(); - it != nProxy.pagesByTrack[track].end(); it++){ - if (it->first <= keyNum){ - pageNumber = it->first; - pageSize = it->second.keyNum; + } + + std::string getXMLValue(const std::string &xml, const std::string &field){ + size_t offset = xml.find("<" + field + ">") + field.size() + 2; + std::string res = xml.substr(offset, xml.find("") - offset); + getCData(res); + return res; + } + + void Input::handleBuyDRM(){ + std::string contentID = config->getString("contentid"); + if (contentID == ""){contentID = Secure::md5(config->getString("streamname"));} + std::string keyID = config->getString("keyid"); + if (keyID == ""){keyID = Secure::md5(contentID);} + + std::stringstream soap; + soap << "" + "52B11D80-21E2-4281-BB83-1B4191002534" + "" + "5.0.0.2smooth" + "MistServer " RELEASE "_" PACKAGE_VERSION ""; + soap << config->getString("buydrm"); + soap << ""; + soap << formatGUID(contentID); + soap << ""; + soap << formatGUID(keyID); + soap << "truetrue"; + soap << config->getString("streamname"); + soap << "" + "]]>" + ""; + INFO_MSG("Sending soap request %s", soap.str().c_str()); + + HTTP::Downloader dld; + dld.setHeader("Content-Type", "text/xml; charset=utf-8"); + dld.setHeader("SOAPAction", "\"http://tempuri.org/ISmoothPackager/RequestEncryptionInfo\""); + dld.post(HTTP::URL("https://packager.licensekeyserver.com/pck/"), soap.str()); + const std::string &result = dld.getHTTP().body; + std::string replaced; + replaced.reserve(result.size()); + size_t i = 0; + while (i < result.size()){ + if (result.at(i) == '&'){ + if (result.at(i + 1) == 'l' && result.at(i + 2) == 't'){ + replaced += '<'; + }else if (result.at(i + 1) == 'g' && result.at(i + 2) == 't'){ + replaced += '>'; + }else if (result.at(i + 1) == '#' && result.at(i + 2) == 'x' && result.at(i + 3) == 'D'){ + replaced += '\r'; }else{ - break; + replaced.append(result.data() + i, result.find(';', i) - i); } + i = result.find(';', i) + 1; + continue; } - pageCounter[track][pageNumber] = 15; - // If we're less than 10% off from the next page, make sure the next is also buffered. - if (keyNum + pageSize / 10 > pageNumber + pageSize){ - MEDIUM_MSG("Pre-buffering next page! (%u+%u/10 > %u+%u)", keyNum, pageSize, pageNumber, pageSize); - return bufferFrame(track, pageNumber + pageSize + 1); + replaced += result.at(i); + i++; + } + while (replaced.find('\r') != std::string::npos){replaced.erase(replaced.find('\r'), 1);} + while (replaced.find('\n') != std::string::npos){replaced.erase(replaced.find('\n'), 1);} + INFO_MSG("ContentKey: %s", getXMLValue(replaced, "ContentKey").c_str()); + INFO_MSG("WVHeader: %s", getXMLValue(replaced, "WVHeader").c_str()); + INFO_MSG("PRHeader: %s", getXMLValue(replaced, "PRHeader").c_str()); + std::string cKey = Encodings::Base64::decode(getXMLValue(replaced, "ContentKey")); + config->getOption("encryption", true).append(keyID + ":" + Encodings::Hex::encode(cKey)); + config->getOption("widevine", true).append(getXMLValue(replaced, "WVHeader")); + config->getOption("playready", true).append(getXMLValue(replaced, "PRHeader")); + INFO_MSG("Set encryption to %s", config->getString("encryption").c_str()); + } + + bool Input::bufferFrame(size_t idx, uint32_t keyNum){ + if (M.getLive()){return true;} + HIGH_MSG("Buffering track %zu, key %" PRIu32, idx, keyNum); + size_t sourceIdx = M.getSourceTrack(idx); + if (sourceIdx == INVALID_TRACK_ID){sourceIdx = idx;} + + const Util::RelAccX &tPages = M.pages(idx); + DTSC::Keys keys(M.keys(idx)); + uint32_t keyCount = keys.getValidCount(); + if (!tPages.getEndPos()){ + WARN_MSG("No pages for track %zu found! Cancelling bufferFrame", idx); + return false; + } + if (keyNum > keyCount){ + // End of movie here, returning true to avoid various error messages + if (keyNum > keyCount + 1){ + WARN_MSG("Key %" PRIu32 " on track %zu is higher than total (%" PRIu32 + "). Cancelling buffering.", + keyNum, idx, keyCount); } - VERYHIGH_MSG("Track %u, key %u is already buffered in page %d. Cancelling bufferFrame", track, - keyNum, pageNumber); return true; } - if (!nProxy.pagesByTrack.count(track)){ - WARN_MSG("No pages for track %u found! Cancelling bufferFrame", track); - return false; + uint64_t pageIdx = 0; + for (uint64_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ + if (tPages.getInt("firstkey", i) > keyNum) break; + pageIdx = i; + } + uint32_t pageNumber = tPages.getInt("firstkey", pageIdx); + if (isBuffered(idx, pageNumber)){ + // get corresponding page number + pageCounter[idx][pageNumber] = 15; + VERYHIGH_MSG("Track %zu, key %" PRIu32 "is already buffered in page %" PRIu32 + ". Cancelling bufferFrame", + idx, keyNum, pageNumber); + return true; } // Update keynum to point to the corresponding page uint64_t bufferTimer = Util::bootMS(); - MEDIUM_MSG("Loading key %u from page %lu", keyNum, - (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first); - keyNum = (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first; - if (!bufferStart(track, keyNum)){ + keyNum = pageNumber; + if (!bufferStart(idx, keyNum)){ WARN_MSG("bufferStart failed! Cancelling bufferFrame"); return false; } - std::stringstream trackSpec; - trackSpec << track; - trackSelect(trackSpec.str()); - bool isSrt = (hasSrt && track == myMeta.tracks.rbegin()->first); + uint64_t keyTime = keys.getTime(keyNum); + userSelect[idx].reload(streamName, idx); + + bool isSrt = (hasSrt && idx == srtTrack); if (isSrt){ - srtTrack = track; srtSource.clear(); srtSource.seekg(0, srtSource.beg); srtPack.null(); }else{ - seek(myMeta.tracks[track].keys[keyNum - 1].getTime()); + seek(keyTime, sourceIdx); } - long long unsigned int stopTime = myMeta.tracks[track].lastms + 1; - if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum){ - stopTime = myMeta.tracks[track].keys[keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum].getTime(); + uint64_t stopTime = M.getLastms(idx) + 1; + if (pageIdx != tPages.getEndPos() - 1){ + stopTime = keys.getTime(pageNumber + tPages.getInt("keycount", pageIdx)); } - HIGH_MSG("Playing from %llu to %llu", myMeta.tracks[track].keys[keyNum - 1].getTime(), stopTime); + HIGH_MSG("Playing from %" PRIu64 " to %" PRIu64, keyTime, stopTime); if (isSrt){ getNextSrt(); // in case earlier seeking was inprecise, seek to the exact point - while (srtPack && - srtPack.getTime() < (unsigned long long)myMeta.tracks[track].keys[keyNum - 1].getTime()){ - getNextSrt(); - } + while (srtPack && srtPack.getTime() < keys.getTime(keyNum)){getNextSrt();} }else{ - getNext(); + getNext(sourceIdx); // in case earlier seeking was inprecise, seek to the exact point - while (thisPacket && thisPacket.getTime() < - (unsigned long long)myMeta.tracks[track].keys[keyNum - 1].getTime()){ - DONTEVEN_MSG("Skipping packet: %u@%" PRIu64 ", %zub", track, thisPacket.getTime(), - thisPacket.getDataLen()); - getNext(); - } + while (thisPacket && thisPacket.getTime() < keys.getTime(keyNum)){getNext(sourceIdx);} } uint64_t lastBuffered = 0; - uint64_t packCounter = 0; + uint32_t packCounter = 0; uint64_t byteCounter = 0; + std::string encryption; if (isSrt){ while (srtPack && srtPack.getTime() < stopTime){ if (srtPack.getTime() >= lastBuffered){ - bufferNext(srtPack); + char *data; + size_t dataLen; + srtPack.getString("data", data, dataLen); + bufferNext(srtPack.getTime(), 0, idx, data, dataLen, srtPack.getInt("bpos"), + srtPack.getFlag("keyframe")); ++packCounter; byteCounter += srtPack.getDataLen(); lastBuffered = srtPack.getTime(); @@ -1071,62 +1199,91 @@ namespace Mist{ }else{ while (thisPacket && thisPacket.getTime() < stopTime){ if (thisPacket.getTime() >= lastBuffered){ - DONTEVEN_MSG("Buffering packet: %u@%" PRIu64 ", %zub", track, thisPacket.getTime(), - thisPacket.getDataLen()); - bufferNext(thisPacket); + if (sourceIdx != idx){ + if (encryption.find(":") != std::string::npos || M.getEncryption(idx).find(":") != std::string::npos){ + if (encryption == ""){ + encryption = M.getEncryption(idx); + std::string encryptionKey = + Encodings::Hex::decode(encryption.substr(encryption.find(":") + 1)); + aesCipher.setEncryptKey(encryptionKey.c_str()); + } + if (encryption.substr(0, encryption.find('/')) == "CTR128"){ + DTSC::Packet encPacket = aesCipher.encryptPacketCTR( + M, thisPacket, M.getIvec(idx) + M.getPartIndex(thisPacket, idx), idx); + thisPacket = encPacket; + }else if (encryption.substr(0, encryption.find('/')) == "CBC128"){ + char ivec[] ={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + Bit::htobll(ivec + 8, M.getIvec(idx) + M.getPartIndex(thisPacket, idx)); + DTSC::Packet encPacket = aesCipher.encryptPacketCBC(M, thisPacket, ivec, idx); + thisPacket = encPacket; + } + }else{ + thisPacket = DTSC::Packet(thisPacket, idx); + } + } + char *data; + size_t dataLen; + thisPacket.getString("data", data, dataLen); + bufferNext(thisPacket.getTime(), thisPacket.getInt("offset"), idx, data, dataLen, + thisPacket.getInt("bpos"), thisPacket.getFlag("keyframe")); ++packCounter; byteCounter += thisPacket.getDataLen(); lastBuffered = thisPacket.getTime(); } - getNext(); + getNext(sourceIdx); } } - bufferFinalize(track); + bufferFinalize(idx); bufferTimer = Util::bootMS() - bufferTimer; - DEBUG_MSG(DLVL_DEVEL, "Done buffering page %d (%llu packets, %llu bytes, %llu-%llums -> %llums) for track %d (%s) in %llums", - keyNum, packCounter, byteCounter, myMeta.tracks[track].keys[keyNum - 1].getTime(), - stopTime, lastBuffered, track, myMeta.tracks[track].codec.c_str(), bufferTimer); - pageCounter[track][keyNum] = 15; + INFO_MSG("Track %zu, page %" PRIu32 " (%" PRIu64 " - %" PRIu64 " ms) buffered in %" PRIu64 "ms", + idx, keyNum, tPages.getInt("firsttime", pageIdx), thisPacket.getTime(), bufferTimer); + INFO_MSG(" (%" PRIu32 "/%" PRIu64 " parts, %" PRIu64 " bytes)", packCounter, + tPages.getInt("parts", pageIdx), byteCounter); + pageCounter[idx][keyNum] = 15; return true; } bool Input::atKeyFrame(){ - static std::map lastSeen; + static std::map lastSeen; + size_t idx = thisPacket.getTrackId(); // not in keyTimes? We're not at a keyframe. - unsigned int c = keyTimes[thisPacket.getTrackId()].count(thisPacket.getTime()); - if (!c){return false;} + if (!keyTimes[idx].count(thisPacket.getTime())){return false;} // skip double times - if (lastSeen.count(thisPacket.getTrackId()) && lastSeen[thisPacket.getTrackId()] == thisPacket.getTime()){ - return false; - } + if (lastSeen.count(idx) && lastSeen[idx] == thisPacket.getTime()){return false;} // set last seen, and return true - lastSeen[thisPacket.getTrackId()] = thisPacket.getTime(); + lastSeen[idx] = thisPacket.getTime(); return true; } - void Input::play(int until){ - playing = -1; - playUntil = until; - initialTime = 0; - } - - void Input::playOnce(){ - if (playing <= 0){playing = 1;} - ++playing; - } - - void Input::quitPlay(){playing = 0;} - bool Input::readExistingHeader(){ - DTSC::File tmpdtsh(config->getString("input") + ".dtsh"); - if (!tmpdtsh){return false;} - if (tmpdtsh.getMeta().version != DTSH_VERSION){ - INFO_MSG("Updating wrong version header file from version %" PRIu16 " to %d", - tmpdtsh.getMeta().version, DTSH_VERSION); + char pageName[NAME_BUFFER_SIZE]; + snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_META, config->getString("streamname").c_str()); + IPC::sharedPage sp(pageName, 0, false, false); + if (sp){ + sp.close(); + meta.reInit(config->getString("streamname"), false); + if (meta){ + meta.setMaster(true); + INFO_MSG("Read existing header"); + return true; + } + } + meta.reInit(config->getString("streamname"), config->getString("input") + ".dtsh"); + if (meta.version != DTSH_VERSION){ + INFO_MSG("Updating wrong version header file from version %u to %u", meta.version, DTSH_VERSION); return false; } - myMeta = tmpdtsh.getMeta(); - return true; + return meta; } + bool Input::keepAlive(){ + if (!userSelect.size()){return config->is_active;} + + bool isAlive = false; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (it->second.isAlive()){isAlive = true;} + it->second.keepAlive(); + } + return isAlive && config->is_active; + } }// namespace Mist diff --git a/src/input/input.h b/src/input/input.h index f4fbdfdf..41bfb1e4 100644 --- a/src/input/input.h +++ b/src/input/input.h @@ -1,9 +1,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -13,9 +15,9 @@ namespace Mist{ struct booking{ - int first; - int curKey; - int curPart; + uint32_t first; + uint32_t curKey; + uint32_t curPart; }; class Input : public InOutBase{ @@ -26,61 +28,63 @@ namespace Mist{ virtual int boot(int argc, char *argv[]); virtual ~Input(){}; + bool keepAlive(); + void reloadClientMeta(); + bool hasMeta() const; static Util::Config *config; virtual bool needsLock(){return !config->getBool("realtime");} protected: - static void callbackWrapper(char *data, size_t len, unsigned int id); virtual bool checkArguments() = 0; - virtual bool readHeader() = 0; + virtual bool readHeader(); virtual bool needHeader(){return !readExistingHeader();} virtual bool preRun(){return true;} virtual bool isSingular(){return !config->getBool("realtime");} virtual bool readExistingHeader(); virtual bool atKeyFrame(); - virtual void getNext(bool smart = true){} - virtual void seek(int seekTime){}; + virtual void getNext(size_t idx = INVALID_TRACK_ID){} + virtual void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){} virtual void finish(); virtual bool keepRunning(); virtual bool openStreamSource(){return readHeader();} virtual void closeStreamSource(){} virtual void parseStreamHeader(){} - void play(int until = 0); - void playOnce(); - void quitPlay(); void checkHeaderTimes(std::string streamFile); 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 serve(); 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 realtimeMainLoop(); bool isAlwaysOn(); + virtual void userLeadIn(); + virtual void userOnActive(size_t id); + virtual void userOnDisconnect(size_t id); + virtual void userLeadOut(); + 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; JSON::Value capa; - std::map > keyTimes; int64_t timeOffset; + std::map > keyTimes; // Create server for user pages - IPC::sharedServer userPage; + Comms::Users users; + size_t connectedUsers; + + Encryption::AES aesCipher; + IPC::sharedPage streamStatus; - std::map > pageCounter; + std::map > pageCounter; static Input *singleton; @@ -93,6 +97,7 @@ namespace Mist{ DTSC::Packet srtPack; uint64_t simStartTime; - }; + void handleBuyDRM(); + }; }// namespace Mist diff --git a/src/input/input_av.cpp b/src/input/input_av.cpp index 2e378234..82fda21a 100644 --- a/src/input/input_av.cpp +++ b/src/input/input_av.cpp @@ -72,7 +72,7 @@ namespace Mist{ if (ret != 0){ char 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 } @@ -81,7 +81,7 @@ namespace Mist{ if (ret < 0){ char 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 true; @@ -160,12 +160,12 @@ namespace Mist{ return true; } - void inputAV::getNext(bool smart){ + void inputAV::getNext(){ AVPacket packet; while (av_read_frame(pFormatCtx, &packet) >= 0){ // filter tracks we don't care about 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; } AVStream *strm = pFormatCtx->streams[packet.stream_index]; @@ -187,7 +187,7 @@ namespace Mist{ thisPacket.null(); preRun(); // failure :-( - DEBUG_MSG(DLVL_FAIL, "getNext failed"); + FAIL_MSG("getNext failed"); } void inputAV::seek(int seekTime){ diff --git a/src/input/input_av.h b/src/input/input_av.h index aa425fb6..b6960fd7 100644 --- a/src/input/input_av.h +++ b/src/input/input_av.h @@ -23,7 +23,7 @@ namespace Mist{ bool checkArguments(); bool preRun(); bool readHeader(); - void getNext(bool smart = true); + void getNext(); void seek(int seekTime); void trackSelect(std::string trackSpec); diff --git a/src/input/input_balancer.cpp b/src/input/input_balancer.cpp index 8545338a..007caae6 100644 --- a/src/input/input_balancer.cpp +++ b/src/input/input_balancer.cpp @@ -47,7 +47,7 @@ namespace Mist{ Socket::Connection balConn(url.host, url.getPort(), true); 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{ HTTP::Parser http; http.url = "/" + url.path; diff --git a/src/input/input_buffer.cpp b/src/input/input_buffer.cpp index b6792671..6a7d6ab4 100644 --- a/src/input/input_buffer.cpp +++ b/src/input/input_buffer.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include "input_buffer.h" @@ -31,6 +30,7 @@ namespace Mist{ capa["optional"].removeMember("realtime"); + finalMillis = 0; liveMeta = 0; capa["name"] = "Buffer"; JSON::Value option; @@ -114,8 +114,6 @@ namespace Mist{ capa["codecs"][0u][0u].append("*"); capa["codecs"][0u][1u].append("*"); capa["codecs"][0u][2u].append("*"); - isBuffer = true; - singleton = this; bufferTime = 50000; cutTime = 0; segmentSize = 1900; @@ -125,33 +123,6 @@ namespace Mist{ inputBuffer::~inputBuffer(){ config->is_active = false; - if (myMeta.tracks.size()){ - DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes"); - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - std::map &locations = bufferLocations[it->first]; - if (!nProxy.metaPages.count(it->first) || !nProxy.metaPages[it->first].mapped){continue;} - // First detect all entries on metaPage - for (int i = 0; i < 8192; i += 8){ - char *tmpOffset = nProxy.metaPages[it->first].mapped + i; - if (Bit::btohl(tmpOffset) == 0 && Bit::btohl(tmpOffset + 4) == 0){continue;} - unsigned long keyNum = Bit::btohl(tmpOffset); - - // Add an entry into bufferLocations[tNum] for the pages we haven't handled yet. - if (!locations.count(keyNum)){locations[keyNum].curOffset = 0;} - locations[keyNum].pageNum = keyNum; - locations[keyNum].keyNum = Bit::btohl(tmpOffset + 4); - } - for (std::map::iterator it2 = locations.begin(); - it2 != locations.end(); it2++){ - char thisPageName[NAME_BUFFER_SIZE]; - snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, - config->getString("streamname").c_str(), it->first, it2->first); - IPC::sharedPage erasePage(thisPageName, 20971520); - erasePage.master = true; - } - } - } if (liveMeta){ liveMeta->unlink(); delete liveMeta; @@ -163,83 +134,59 @@ namespace Mist{ void inputBuffer::onCrash(){ WARN_MSG("Buffer crashed. Cleaning."); streamName = config->getString("streamname"); - char pageName[NAME_BUFFER_SIZE]; - // Set userpage to all 0xFF's, will disconnect all current clients. - snprintf(pageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); - std::string baseName = pageName; - for (long unsigned i = 0; i < 15; ++i){ - unsigned int size = std::min(((8192 * 2) << i), (32 * 1024 * 1024)); - IPC::sharedPage tmp(std::string(baseName + (char)(i + (int)'A')), size, false, false); - if (tmp.mapped){ - tmp.master = true; - WARN_MSG("Wiping %s", std::string(baseName + (char)(i + (int)'A')).c_str()); - memset(tmp.mapped, 0xFF, size); + // Scoping to clear up users page + { + Comms::Users cleanUsers; + cleanUsers.reload(streamName); + for (size_t i = cleanUsers.firstValid(); i < cleanUsers.endValid(); ++i){ + cleanUsers.setStatus(COMM_STATUS_INVALID, i); } + cleanUsers.setMaster(true); } // Delete the live stream semaphore, if any. if (liveMeta){liveMeta->unlink();} + // Scoping to clear up metadata pages { - // Delete the stream index metapage. - snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); - IPC::sharedPage erasePage(pageName, DEFAULT_STRM_PAGE_SIZE, false, false); - erasePage.master = true; - } - // Delete most if not all temporary track metadata pages. - for (long unsigned i = 1001; i <= 1024; ++i){ - snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_META, streamName.c_str(), i); - IPC::sharedPage erasePage(pageName, 1024, false, false); - erasePage.master = true; - } - // Delete most if not all track indexes and data pages. - for (long unsigned i = 1; i <= 24; ++i){ - snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), i); - IPC::sharedPage indexPage(pageName, SHM_TRACK_INDEX_SIZE, false, false); - indexPage.master = true; - if (indexPage.mapped){ - char *mappedPointer = indexPage.mapped; - for (int j = 0; j < 8192; j += 8){ - char *tmpOffset = mappedPointer + j; - if (Bit::btohl(tmpOffset) == 0 && Bit::btohl(tmpOffset + 4) == 0){continue;} - unsigned long keyNum = Bit::btohl(tmpOffset); - snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), i, keyNum); - IPC::sharedPage erasePage(pageName, 1024, false, false); - erasePage.master = true; - } - } + DTSC::Meta cleanMeta(streamName, false); + cleanMeta.setMaster(true); } } /// Fills the details variable with details about the current buffer contents - void inputBuffer::fillBufferDetails(JSON::Value &details){ + void inputBuffer::fillBufferDetails(JSON::Value &details) const{ // clear the reference of old data, first details.null(); bool hasH264 = false; bool hasAAC = false; std::stringstream issues; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - JSON::Value &track = details[it->second.getWritableIdentifier()]; - track["kbits"] = (uint64_t)((double)it->second.bps * 8 / 1024); - track["codec"] = it->second.codec; + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + size_t i = *it; + JSON::Value &track = details[M.getTrackIdentifier(i)]; + std::string codec = M.getCodec(i); + std::string type = M.getType(i); + track["kbits"] = M.getBps(i) * 8 / 1024; + track["codec"] = codec; uint32_t shrtest_key = 0xFFFFFFFFul; uint32_t longest_key = 0; uint32_t shrtest_prt = 0xFFFFFFFFul; uint32_t longest_prt = 0; uint32_t shrtest_cnt = 0xFFFFFFFFul; uint32_t longest_cnt = 0; - for (std::deque::iterator k = it->second.keys.begin(); k != it->second.keys.end(); k++){ - if (!k->getLength()){continue;} - if (k->getLength() > longest_key){longest_key = k->getLength();} - if (k->getLength() < shrtest_key){shrtest_key = k->getLength();} - if (k->getParts() > longest_cnt){longest_cnt = k->getParts();} - if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();} - if ((k->getLength() / k->getParts()) > longest_prt){ - longest_prt = (k->getLength() / k->getParts()); - } - if ((k->getLength() / k->getParts()) < shrtest_prt){ - shrtest_prt = (k->getLength() / k->getParts()); - } + DTSC::Keys keys(M.keys(i)); + uint32_t firstKey = keys.getFirstValid(); + uint32_t endKey = keys.getEndValid() - 1; + for (int k = firstKey; k < endKey; k++){ + uint64_t kDur = keys.getDuration(k); + uint64_t kParts = keys.getParts(k); + if (!kDur){continue;} + if (kDur > longest_key){longest_key = kDur;} + if (kDur < shrtest_key){shrtest_key = kDur;} + if (kParts > longest_cnt){longest_cnt = kParts;} + if (kParts < shrtest_cnt){shrtest_cnt = kParts;} + if ((kDur / kParts) > longest_prt){longest_prt = (kDur / kParts);} + if ((kDur / kParts) < shrtest_prt){shrtest_prt = (kDur / kParts);} } track["keys"]["ms_min"] = shrtest_key; track["keys"]["ms_max"] = longest_key; @@ -248,20 +195,20 @@ namespace Mist{ track["keys"]["frames_min"] = shrtest_cnt; track["keys"]["frames_max"] = longest_cnt; 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){ - 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 (it->second.codec == "H264"){hasH264 = true;} - if (it->second.type == "video"){ - track["width"] = it->second.width; - track["height"] = it->second.height; - track["fpks"] = it->second.fpks; + if (codec == "AAC"){hasAAC = true;} + if (codec == "H264"){hasH264 = true;} + if (type == "video"){ + track["width"] = M.getWidth(i); + track["height"] = M.getHeight(i); + track["fpks"] = M.getFpks(i); } } - if ((hasAAC || hasH264) && myMeta.tracks.size() > 1){ + if ((hasAAC || hasH264) && validTracks.size() > 1){ if (!hasAAC){issues << "HLS no audio!";} if (!hasH264){issues << "HLS no video!";} } @@ -272,7 +219,7 @@ namespace Mist{ /*LTS-START*/ static bool liveBW(const char *param, const void *bwPtr){ if (!param || !bwPtr){return false;} - INFO_MSG("Comparing %s to %lu", param, *((uint32_t *)bwPtr)); + INFO_MSG("Comparing %s to %" PRIu32, param, *((uint32_t *)bwPtr)); return JSON::Value(param).asInt() <= *((uint32_t *)bwPtr); } /*LTS-END*/ @@ -290,26 +237,28 @@ namespace Mist{ /// ~~~~~~~~~~~~~~~ void inputBuffer::updateMeta(){ static bool wentDry = false; - static long long unsigned int lastFragCount = 0xFFFFull; + static uint64_t lastFragCount = 0xFFFFull; static uint32_t lastBPS = 0; /*LTS*/ uint32_t currBPS = 0; - long long unsigned int firstms = 0xFFFFFFFFFFFFFFFFull; - long long unsigned int lastms = 0; - long long unsigned int fragCount = 0xFFFFull; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - currBPS += it->second.bps; /*LTS*/ - if (it->second.type == "meta" || !it->second.type.size()){continue;} - if (it->second.init.size()){ - if (!initData.count(it->first) || initData[it->first] != it->second.init){ - initData[it->first] = it->second.init; - } + uint64_t firstms = 0xFFFFFFFFFFFFFFFFull; + uint64_t lastms = 0; + uint64_t fragCount = 0xFFFFull; + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + size_t i = *it; + currBPS += M.getBps(i); /*LTS*/ + if (M.getType(i) == "meta" || !M.getType(i).size()){continue;} + std::string init = M.getInit(i); + // Prevent init data from being thrown away + if (init.size()){ + if (!initData.count(i) || initData[i] != init){initData[i] = init;} }else{ - if (initData.count(it->first)){it->second.init = initData[it->first];} + if (initData.count(i)){meta.setInit(i, initData[i]);} } - if (it->second.fragments.size() < fragCount){fragCount = it->second.fragments.size();} - if (it->second.firstms < firstms){firstms = it->second.firstms;} - if (it->second.lastms > lastms){lastms = it->second.lastms;} + DTSC::Fragments fragments(M.fragments(i)); + if (fragments.getEndValid() < fragCount){fragCount = fragments.getEndValid();} + if (M.getFirstms(i) < firstms){firstms = M.getFirstms(i);} + if (M.getLastms(i) > lastms){lastms = M.getLastms(i);} } /*LTS-START*/ if (currBPS != lastBPS){ @@ -321,7 +270,7 @@ namespace Mist{ if (!Triggers::doTrigger("LIVE_BANDWIDTH", payload, streamName)){ WARN_MSG("Shutting down buffer because bandwidth limit reached!"); config->is_active = false; - userPage.finishEach(); + userSelect.clear(); } } } @@ -346,42 +295,9 @@ namespace Mist{ lastFragCount = fragCount; } /*LTS-END*/ - myMeta.bufferWindow = lastms - firstms; - myMeta.vod = false; - myMeta.live = true; - if (!liveMeta){ - static char liveSemName[NAME_BUFFER_SIZE]; - snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str()); - liveMeta = new IPC::semaphore(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 8); - } - liveMeta->wait(); - liveMeta->wait(); - liveMeta->wait(); - liveMeta->wait(); - liveMeta->wait(); - liveMeta->wait(); - liveMeta->wait(); - liveMeta->wait(); - - if (!nProxy.metaPages.count(0) || !nProxy.metaPages[0].mapped){ - char pageName[NAME_BUFFER_SIZE]; - snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); - nProxy.metaPages[0].init(pageName, DEFAULT_STRM_PAGE_SIZE, true); - nProxy.metaPages[0].master = false; - } - myMeta.writeTo(nProxy.metaPages[0].mapped); - memset(nProxy.metaPages[0].mapped + myMeta.getSendLen(), 0, - (nProxy.metaPages[0].len > myMeta.getSendLen() - ? std::min((size_t)(nProxy.metaPages[0].len - myMeta.getSendLen()), (size_t)4) - : 0)); - liveMeta->post(); - liveMeta->post(); - liveMeta->post(); - liveMeta->post(); - liveMeta->post(); - liveMeta->post(); - liveMeta->post(); - liveMeta->post(); + finalMillis = lastms; + meta.setBufferWindow(lastms - firstms); + meta.setLive(true); } /// Checks if removing a key from this track is allowed/safe, and if so, removes it. @@ -392,80 +308,42 @@ namespace Mist{ /// * first fragment hasn't been at least lastms-firstms ms in buffer /// * less than 8 times the biggest fragment duration is buffered /// If a key was deleted and the first buffered data page is no longer used, it is deleted also. - bool inputBuffer::removeKey(unsigned int tid){ - DTSC::Track &Trk = myMeta.tracks[tid]; + bool inputBuffer::removeKey(size_t tid){ + DTSC::Keys keys(M.keys(tid)); // If this track is empty, abort - if (!Trk.keys.size()){return false;} + if (!keys.getValidCount()){return false;} // the following checks only run if we're not shutting down if (config->is_active){ // Make sure we have at least 4 whole fragments at all times, - if (Trk.fragments.size() < 5){return false;} + DTSC::Fragments fragments(M.fragments(tid)); + if (fragments.getValidCount() < 5){return false;} // ensure we have each fragment buffered for at least the whole bufferTime - if (!Trk.secsSinceFirstFragmentInsert() || (Trk.lastms - Trk.firstms) < bufferTime){ - return false; - } - if (Trk.fragments.size() > 2){ + if ((M.getLastms(tid) - M.getFirstms(tid)) < bufferTime){return false;} + uint32_t firstFragment = fragments.getFirstValid(); + uint32_t endFragment = fragments.getEndValid(); + if (endFragment - firstFragment > 2){ /// Make sure we have at least 8X the target duration. // The target duration is the biggest fragment, rounded up to whole seconds. - uint32_t targetDuration = (Trk.biggestFragment() / 1000 + 1) * 1000; + uint64_t targetDuration = (M.biggestFragment(tid) / 1000 + 1) * 1000; // The start is the third fragment's begin - uint32_t fragStart = Trk.getKey((++(++Trk.fragments.begin()))->getNumber()).getTime(); + uint64_t fragStart = keys.getTime(fragments.getFirstKey(firstFragment)); // The end is the last fragment's begin - uint32_t fragEnd = Trk.getKey(Trk.fragments.rbegin()->getNumber()).getTime(); - if ((fragEnd - fragStart) < targetDuration * 8){return false;} + uint64_t fragEnd = keys.getTime(fragments.getFirstKey(endFragment - 1)); + if ((fragEnd - fragStart) < (targetDuration * 8)){return false;} } } // Alright, everything looks good, let's delete the key and possibly also fragment - Trk.removeFirstKey(); - // if there is more than one page buffered for this track... - if (bufferLocations[tid].size() > 1){ - // Check if the first key starts on the second page or higher - if (Trk.keys[0].getNumber() >= (++(bufferLocations[tid].begin()))->first || !config->is_active){ - // If so, we can delete the first page entirely - HIGH_MSG("Erasing track %d, keys %lu-%lu from buffer", tid, bufferLocations[tid].begin()->first, - bufferLocations[tid].begin()->first + bufferLocations[tid].begin()->second.keyNum - 1); - bufferRemove(tid, bufferLocations[tid].begin()->first); - - nProxy.curPageNum.erase(tid); - char thisPageName[NAME_BUFFER_SIZE]; - snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), - (unsigned long)tid, bufferLocations[tid].begin()->first); - nProxy.curPage[tid].init(thisPageName, 20971520); - nProxy.curPage[tid].master = true; - nProxy.curPage.erase(tid); - - bufferLocations[tid].erase(bufferLocations[tid].begin()); - }else{ - VERYHIGH_MSG("%lu still on first page (%lu - %lu)", myMeta.tracks[tid].keys[0].getNumber(), - bufferLocations[tid].begin()->first, - bufferLocations[tid].begin()->first + bufferLocations[tid].begin()->second.keyNum - 1); - } - } + meta.removeFirstKey(tid); return true; } - void inputBuffer::eraseTrackDataPages(unsigned long tid){ - if (!bufferLocations.count(tid)){return;} - for (std::map::iterator it = bufferLocations[tid].begin(); - it != bufferLocations[tid].end(); it++){ - char thisPageName[NAME_BUFFER_SIZE]; - snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, - config->getString("streamname").c_str(), tid, it->first); - nProxy.curPage[tid].init(thisPageName, 20971520, false, false); - nProxy.curPage[tid].master = true; - nProxy.curPage.erase(tid); - } - bufferLocations.erase(tid); - nProxy.metaPages[tid].master = true; - nProxy.metaPages.erase(tid); - } - void inputBuffer::finish(){ - if (myMeta.tracks.size()){ + if (M.getValidTracks().size()){ /*LTS-START*/ - if (myMeta.bufferWindow){ + if (M.getBufferWindow()){ if (Triggers::shouldTrigger("STREAM_BUFFER")){ - std::string payload = config->getString("streamname") + "\nEMPTY"; + std::string payload = + config->getString("streamname") + "\nEMPTY\n" + JSON::Value(finalMillis).asString(); Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); } } @@ -473,146 +351,116 @@ namespace Mist{ } Input::finish(); updateMeta(); - if (bufferLocations.size()){ - std::set toErase; - for (std::map >::iterator it = - bufferLocations.begin(); - it != bufferLocations.end(); it++){ - toErase.insert(it->first); - } - for (std::set::iterator it = toErase.begin(); it != toErase.end(); ++it){ - eraseTrackDataPages(*it); - } - } - for (std::map::iterator it = nProxy.metaPages.begin(); - it != nProxy.metaPages.end(); ++it){ - it->second.master = true; - } } - /// \triggers - /// The `"STREAM_TRACK_REMOVE"` trigger is stream-specific, and is ran whenever a track is fully - /// removed from a live stream buffer. It cannot be cancelled. Its payload is: - /// ~~~~~~~~~~~~~~~ - /// streamname - /// trackID - /// ~~~~~~~~~~~~~~~ + void inputBuffer::removeTrack(size_t tid){ + size_t lastUser = users.endValid(); + for (size_t i = users.firstValid(); i < lastUser; ++i){ + if (users.getStatus(i) == COMM_STATUS_INVALID){continue;} + if (!(users.getStatus(i) & COMM_STATUS_SOURCE)){continue;} + if (users.getTrack(i) != tid){continue;} + // We have found the right track here (pid matches, and COMM_STATUS_SOURCE set) + users.setStatus(COMM_STATUS_DISCONNECT, i); + break; + } + + curPageNum.erase(tid); + INFO_MSG("Should remove track %zu", tid); + meta.refresh(); + meta.removeTrack(tid); + /*LTS-START*/ + if (!M.getValidTracks().size()){ + if (Triggers::shouldTrigger("STREAM_BUFFER")){ + std::string payload = config->getString("streamname") + "\nEMPTY"; + Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); + } + } + /*LTS-END*/ + } + void inputBuffer::removeUnused(){ + meta.refresh(); // first remove all tracks that have not been updated for too long bool changed = true; while (changed){ changed = false; - long long unsigned int time = Util::bootSecs(); - long long unsigned int compareFirst = 0xFFFFFFFFFFFFFFFFull; - long long unsigned int compareLast = 0; + uint64_t time = Util::bootSecs(); + uint64_t compareFirst = 0xFFFFFFFFFFFFFFFFull; + uint64_t compareLast = 0; std::set activeTypes; + + std::set tracks = M.getValidTracks(); // for tracks that were updated in the last 5 seconds, get the first and last ms edges. - for (std::map::iterator it2 = myMeta.tracks.begin(); - it2 != myMeta.tracks.end(); it2++){ - if ((time - lastUpdated[it2->first]) > 5){continue;} - activeTypes.insert(it2->second.type); - if (it2->second.lastms > compareLast){compareLast = it2->second.lastms;} - if (it2->second.firstms < compareFirst){compareFirst = it2->second.firstms;} + for (std::set::iterator idx = tracks.begin(); idx != tracks.end(); idx++){ + size_t i = *idx; + if ((time - M.getLastUpdated(i)) > 5){continue;} + activeTypes.insert(M.getType(i)); + if (M.getLastms(i) > compareLast){compareLast = M.getLastms(i);} + if (M.getFirstms(i) < compareFirst){compareFirst = M.getFirstms(i);} } - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - // if not updated for an entire buffer duration, or last updated track and this track differ by an entire buffer duration, erase the track. - if ((long long int)(time - lastUpdated[it->first]) > (long long int)(bufferTime / 1000) || - (compareLast && activeTypes.count(it->second.type) && - (long long int)(time - lastUpdated[it->first]) > 5 && - ((compareLast < it->second.firstms && (long long int)(it->second.firstms - compareLast) > bufferTime) || - (compareFirst > it->second.lastms && (long long int)(compareFirst - it->second.lastms) > bufferTime)))){ - unsigned int tid = it->first; + for (std::set::iterator idx = tracks.begin(); idx != tracks.end(); idx++){ + size_t i = *idx; + std::string codec = M.getCodec(i); + std::string type = M.getType(i); + uint64_t firstms = M.getFirstms(i); + uint64_t lastms = M.getLastms(i); + // if not updated for an entire buffer duration, or last updated track and this track differ + // by an entire buffer duration, erase the track. + if ((time - M.getLastUpdated(i) > (bufferTime / 1000) || + (compareLast && activeTypes.count(type) && (time - M.getLastUpdated(i)) > 5 && + ((compareLast < firstms && (firstms - compareLast) > bufferTime) || + (compareFirst > lastms && (compareFirst - lastms) > bufferTime))))){ // erase this track - if ((long long int)(time - lastUpdated[it->first]) > (long long int)(bufferTime / 1000)){ - WARN_MSG("Erasing %s track %d (%s/%s) because not updated for %ds (> %ds)", - streamName.c_str(), it->first, it->second.type.c_str(), it->second.codec.c_str(), - (long long int)(time - lastUpdated[it->first]), (long long int)(bufferTime / 1000)); + if ((time - M.getLastUpdated(i)) > (bufferTime / 1000)){ + WARN_MSG("Erasing %s track %zu (%s/%s) because not updated for %" PRIu64 "s (> %" PRIu64 "s)", + streamName.c_str(), i, type.c_str(), codec.c_str(), time - M.getLastUpdated(i), + bufferTime / 1000); }else{ - WARN_MSG("Erasing %s inactive track %u (%s/%s) because it was inactive for 5+ seconds " - "and contains data (%us - %us), while active tracks are (%us - %us), which is " - "more than %us seconds apart.", - streamName.c_str(), it->first, it->second.type.c_str(), - it->second.codec.c_str(), it->second.firstms / 1000, it->second.lastms / 1000, - compareFirst / 1000, compareLast / 1000, bufferTime / 1000); + WARN_MSG("Erasing %s inactive track %zu (%s/%s) because it was inactive for 5+ seconds " + "and contains data (%" PRIu64 "s - %" PRIu64 + "s), while active tracks are (%" PRIu64 "s - %" PRIu64 + "s), which is more than %" PRIu64 "s seconds apart.", + streamName.c_str(), i, type.c_str(), codec.c_str(), firstms / 1000, + lastms / 1000, compareFirst / 1000, compareLast / 1000, bufferTime / 1000); } - /*LTS-START*/ - if (Triggers::shouldTrigger("STREAM_TRACK_REMOVE")){ - std::string payload = config->getString("streamname") + "\n" + - JSON::Value((uint64_t)it->first).asString() + "\n"; - Triggers::doTrigger("STREAM_TRACK_REMOVE", payload, config->getString("streamname")); - } - /*LTS-END*/ - lastUpdated.erase(tid); - /// \todo Consider replacing with eraseTrackDataPages(it->first)? - while (bufferLocations[tid].size()){ - char thisPageName[NAME_BUFFER_SIZE]; - snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), - (unsigned long)tid, bufferLocations[tid].begin()->first); - nProxy.curPage[tid].init(thisPageName, 20971520); - nProxy.curPage[tid].master = true; - nProxy.curPage.erase(tid); - bufferLocations[tid].erase(bufferLocations[tid].begin()); - } - if (pushLocation.count(it->first)){ - // \todo Debugger says this is null sometimes. It shouldn't be. Figure out why! - // For now, this if will prevent crashes in these cases. - if (pushLocation[it->first]){ - // Reset the userpage, to allow repushing from TS - IPC::userConnection userConn(pushLocation[it->first]); - for (int i = 0; i < SIMUL_TRACKS; i++){ - if (userConn.getTrackId(i) == it->first){ - userConn.setTrackId(i, 0); - userConn.setKeynum(i, 0); - break; - } - } - } - pushLocation.erase(it->first); - } - nProxy.curPageNum.erase(it->first); - nProxy.metaPages[it->first].master = true; - nProxy.metaPages.erase(it->first); - activeTracks.erase(it->first); - myMeta.tracks.erase(it->first); - /*LTS-START*/ - if (!myMeta.tracks.size()){ - if (Triggers::shouldTrigger("STREAM_BUFFER")){ - std::string payload = config->getString("streamname") + "\nEMPTY"; - Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); - } - } - /*LTS-END*/ + meta.refresh(); + removeTrack(i); changed = true; break; } } } + + std::set tracks = M.getValidTracks(); + // find the earliest video keyframe stored - unsigned int firstVideo = 1; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (it->second.type == "video"){ - if (it->second.firstms < firstVideo || firstVideo == 1){firstVideo = it->second.firstms;} + uint64_t videoFirstms = 0xFFFFFFFFFFFFFFFFull; + + for (std::set::iterator idx = tracks.begin(); idx != tracks.end(); idx++){ + size_t i = *idx; + if (M.getType(i) == "video"){ + if (M.getFirstms(i) < videoFirstms){videoFirstms = M.getFirstms(i);} } } - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ + for (std::set::iterator idx = tracks.begin(); idx != tracks.end(); idx++){ + size_t i = *idx; + std::string type = M.getType(i); + DTSC::Keys keys(M.keys(i)); // non-video tracks need to have a second keyframe that is <= firstVideo // firstVideo = 1 happens when there are no tracks, in which case we don't care any more - if (it->second.type != "video"){ - if (it->second.keys.size() < 2 || (it->second.keys[1].getTime() > firstVideo && firstVideo != 1)){ - continue; - } + uint32_t firstKey = keys.getFirstValid(); + uint32_t endKey = keys.getEndValid(); + if (type != "video"){ + if ((endKey - firstKey) < 2 || keys.getTime(firstKey + 1) > videoFirstms){continue;} } // Buffer cutting - while (it->second.keys.size() > 1 && it->second.keys[0].getTime() < cutTime){ - if (!removeKey(it->first)){break;} + while (keys.getValidCount() > 1 && keys.getTime(keys.getFirstValid()) < cutTime){ + if (!removeKey(i)){break;} } // Buffer size management /// \TODO Make sure data has been in the buffer for at least bufferTime after it goes in - while (it->second.keys.size() > 1 && (it->second.lastms - it->second.keys[1].getTime()) > bufferTime){ - if (!removeKey(it->first)){break;} + while (keys.getValidCount() > 1 && (M.getLastms(i) - keys.getTime(keys.getFirstValid() + 1)) > bufferTime){ + if (!removeKey(i)){break;} } } updateMeta(); @@ -627,402 +475,72 @@ namespace Mist{ INFO_MSG("Shutting down buffer because resume mode is disabled and the source disconnected"); if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;} config->is_active = false; - userPage.finishEach(); + userSelect.clear(); } } - /// \triggers - /// The `"STREAM_TRACK_ADD"` trigger is stream-specific, and is ran whenever a new track is added - /// to a live strean buffer. It cannot be cancelled. Its payload is: - /// ~~~~~~~~~~~~~~~ - /// streamname - /// trackID - /// ~~~~~~~~~~~~~~~ - void inputBuffer::userCallback(char *data, size_t len, unsigned int id){ + void inputBuffer::userLeadIn(){ + meta.refresh(); /*LTS-START*/ // Reload the configuration to make sure we stay up to date with changes through the api if (Util::epoch() - lastReTime > 4){preRun();} /*LTS-END*/ - // Static variable keeping track of the next temporary mapping to use for a track. - static int nextTempId = 1001; - // Get the counter of this user - char counter = (*(data - 1)); - // Each user can have at maximum SIMUL_TRACKS elements in their userpage. - IPC::userConnection userConn(data); - for (int index = 0; index < SIMUL_TRACKS; index++){ - // Get the track id from the current element - unsigned long value = userConn.getTrackId(index); - // Skip value 0xFFFFFFFF as this indicates a previously declined track - if (value == 0xFFFFFFFF){continue;} - // Skip value 0 as this indicates an empty track - if (value == 0){continue;} + connectedUsers = 0; - // If the current value indicates a valid trackid, and it is pushed from this user - if (pushLocation[value] == data){ - // Check for timeouts, and erase the track if necessary - if (counter == 126 || counter == 127){ - pushLocation.erase(value); - if (negotiatingTracks.count(value)){negotiatingTracks.erase(value);} - if (activeTracks.count(value)){ - updateTrackMeta(value); - updateMeta(); - activeTracks.erase(value); - if (!config->getBool("resume")){ - bufferLocations.erase(value); - eraseTrackDataPages(value); - }else{ - // finalize key count on page. We can NOT do this through bufferFinalize, as this triggers side effects.... - for (int i = 0; i < 1024; i++){ - int *tmpOffset = (int *)(nProxy.metaPages[value].mapped + (i * 8)); - int keyNum = ntohl(tmpOffset[0]); - int keyAmount = ntohl(tmpOffset[1]); - if (keyAmount == 1000){ - tmpOffset[1] = htonl(myMeta.tracks[value].keys.rbegin()->getNumber() - keyNum + 1); - break; - } - } - } - } - - if (!config->getBool("resume")){ - nProxy.metaPages[value].master = true; - nProxy.metaPages.erase(value); - } - continue; - } - } - // Track is set to "New track request", assign new track id and create shared memory page - // This indicates that the 'current key' part of the element is set to contain the original track id from the pushing process - if (config->is_active && (value & 0x80000000)){ - if (value & 0x40000000){ - unsigned long finalMap = value & ~0xC0000000; - // Register the new track as an active track. - activeTracks.insert(finalMap); - // Register the time of registration as initial value for the lastUpdated field, plus an extra 5 seconds just to be sure. - lastUpdated[finalMap] = Util::bootSecs() + 5; - // Register the user thats is pushing this element - pushLocation[finalMap] = data; - // Initialize the metadata for this track - if (!myMeta.tracks.count(finalMap)){ - DEBUG_MSG(DLVL_MEDIUM, "Inserting metadata for track number %d", finalMap); - - IPC::sharedPage tMeta; - - char tempMetaName[NAME_BUFFER_SIZE]; - snprintf(tempMetaName, NAME_BUFFER_SIZE, SHM_TRACK_META, - config->getString("streamname").c_str(), finalMap); - tMeta.init(tempMetaName, 8388608, false, false); - if (!tMeta){continue;}// abort for now if page doesn't exist yet - - char firstPage[NAME_BUFFER_SIZE]; - snprintf(firstPage, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, - config->getString("streamname").c_str(), finalMap); - nProxy.metaPages[finalMap].init(firstPage, SHM_TRACK_INDEX_SIZE, false, false); - if (!nProxy.metaPages[finalMap]){continue;}// abort for now if page doesn't exist yet - - // The pages exist, now we try to read in the metadata of the track - - // Store the size of the dtsc packet to read. - unsigned int len = ntohl(((int *)tMeta.mapped)[1]); - // Temporary variable, won't be used again - unsigned int tempForReadingMeta = 0; - // Read in the metadata through a temporary JSON object - ///\todo Optimize this part. Find a way to not have to store the metadata in JSON first, but read it from the page immediately - JSON::Value tempJSONForMeta; - JSON::fromDTMI((const char *)tMeta.mapped + 8, len, tempForReadingMeta, tempJSONForMeta); - - tMeta.master = true; - - // Construct a metadata object for the current track - DTSC::Meta trackMeta(tempJSONForMeta); - - myMeta.tracks[finalMap] = trackMeta.tracks.begin()->second; - myMeta.tracks[finalMap].firstms = 0; - myMeta.tracks[finalMap].lastms = 0; - - userConn.setTrackId(index, finalMap); - userConn.setKeynum(index, 0x0000); - - // Update the metadata for this track - updateTrackMeta(finalMap); - hasPush = true; - } - // Update the metadata to reflect all changes - updateMeta(); - // Write the final mapped track number and keyframe number to the user page element - // This is used to resume pushing as well as pushing new tracks - userConn.setTrackId(index, finalMap); - if (myMeta.tracks[finalMap].keys.size()){ - userConn.setKeynum(index, myMeta.tracks[finalMap].keys.rbegin()->getNumber()); - }else{ - userConn.setKeynum(index, 0); - } - continue; - } - // Set the temporary track id for this item, and increase the temporary value for use with the next track - unsigned long long tempMapping = nextTempId++; - // Add the temporary track id to the list of tracks that are currently being negotiated - negotiatingTracks.insert(tempMapping); - // Write the temporary id to the userpage element - userConn.setTrackId(index, tempMapping); - // Obtain the original track number for the pushing process - unsigned long originalTrack = userConn.getKeynum(index); - // Overwrite it with 0xFFFF - userConn.setKeynum(index, 0xFFFF); - DEBUG_MSG(DLVL_HIGH, "Incoming track %lu from pushing process %d has now been assigned temporary id %llu", - originalTrack, id, tempMapping); - } - - // The track id is set to the value of a track that we are currently negotiating about - if (config->is_active && negotiatingTracks.count(value)){ - // If the metadata page for this track is not yet registered, initialize it - if (!nProxy.metaPages.count(value) || !nProxy.metaPages[value].mapped){ - char tempMetaName[NAME_BUFFER_SIZE]; - snprintf(tempMetaName, NAME_BUFFER_SIZE, SHM_TRACK_META, - config->getString("streamname").c_str(), value); - nProxy.metaPages[value].init(tempMetaName, 8388608, false, false); - } - // If this tracks metdata page is not initialize, skip the entire element for now. It will be instantiated later - if (!nProxy.metaPages[value].mapped){ - // remove the negotiation if it has timed out - if (++negotiationTimeout[value] >= 1000){ - negotiatingTracks.erase(value); - negotiationTimeout.erase(value); - } - continue; - } - - // The page exist, now we try to read in the metadata of the track - - // Store the size of the dtsc packet to read. - unsigned int len = ntohl(((int *)nProxy.metaPages[value].mapped)[1]); - // Temporary variable, won't be used again - unsigned int tempForReadingMeta = 0; - // Read in the metadata through a temporary JSON object - ///\todo Optimize this part. Find a way to not have to store the metadata in JSON first, but read it from the page immediately - JSON::Value tempJSONForMeta; - JSON::fromDTMI((const char *)nProxy.metaPages[value].mapped + 8, len, tempForReadingMeta, tempJSONForMeta); - // Construct a metadata object for the current track - DTSC::Meta trackMeta(tempJSONForMeta); - // If the track metadata does not contain the negotiated track, assume the metadata is currently being written, and skip the element for now. It will be instantiated in the next call. - if (!trackMeta.tracks.count(value)){ - // remove the negotiation if it has timed out - if (++negotiationTimeout[value] >= 1000){ - negotiatingTracks.erase(value); - // Set master to true before erasing the page, because we are responsible for cleaning up unused pages - nProxy.metaPages[value].master = true; - nProxy.metaPages.erase(value); - negotiationTimeout.erase(value); - } - continue; - } - - // Get the identifier for the track, and attempt colision detection. - ///\todo Maybe switch to a new form of detecting collisions, especially with regards to multiple audio languages and camera angles. - int collidesWith = -1; - std::string newTrackIdentifier = trackMeta.tracks.find(value)->second.getIdentifier(); - std::string newTrackInit = trackMeta.tracks.find(value)->second.init; - MEDIUM_MSG("Attempting collision detection for track %s", newTrackIdentifier.c_str()); - - // First check for matches on INIT data - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (it->second.init == newTrackInit){ - // It is the first match - if (collidesWith == -1){ - collidesWith = it->first; - } - // More than one match: check if identifier matches - else if (it->second.getIdentifier() == newTrackIdentifier){ - collidesWith = it->first; - break; - } - }else if (it->second.getIdentifier() == newTrackIdentifier){ - collidesWith = it->first; - break; - } - } - // Remove the "negotiate" status in either case - negotiatingTracks.erase(value); - // Set master to true before erasing the page, because we are responsible for cleaning up unused pages - nProxy.metaPages[value].master = true; - nProxy.metaPages.erase(value); - - // Check if the track collides, and whether the track it collides with is active. - if (collidesWith != -1 && activeTracks.count(collidesWith)){/*LTS*/ - // Print a warning message and set the state of the track to rejected. - WARN_MSG("Collision of temporary track %lu with existing track %d detected. Handling as " - "a new valid track.", - value, collidesWith); - collidesWith = -1; - } - uint64_t finalMap = collidesWith; - if (finalMap == -1){ - // No collision has been detected, assign a new final number - finalMap = (myMeta.tracks.size() ? myMeta.tracks.rbegin()->first : 0) + 1; - MEDIUM_MSG("No collision detected for temporary track %lu from user %u, assigning final " - "track number %lu", - value, id, finalMap); - /*LTS-START*/ - if (Triggers::shouldTrigger("STREAM_TRACK_ADD")){ - std::string payload = - config->getString("streamname") + "\n" + JSON::Value(finalMap).asString() + "\n"; - Triggers::doTrigger("STREAM_TRACK_ADD", payload, config->getString("streamname")); - } - } - /*LTS-END*/ - // Resume either if we have more than 1 keyframe on the replacement track (assume it was already pushing before the track "dissapeared") - // or if the firstms of the replacement track is later than the lastms on the existing track - if (!myMeta.tracks.count(finalMap) || trackMeta.tracks.find(value)->second.keys.size() > 1 || - trackMeta.tracks.find(value)->second.firstms >= myMeta.tracks[finalMap].lastms){ - if (myMeta.tracks.count(finalMap) && myMeta.tracks[finalMap].lastms > 0){ - MEDIUM_MSG("Resume of track %lu detected, coming from temporary track %lu of user %u", - finalMap, value, id); - }else{ - MEDIUM_MSG("New track detected, assigned track id %lu, coming from temporary track %lu " - "of user %u", - finalMap, value, id); - if (resumeMode && (myMeta.bufferWindow > 15000)){ - WARN_MSG("Non-resumed track detected; playback will likely not be correct"); - } - } - }else{ - // Otherwise replace existing track - MEDIUM_MSG( - "Replacement of track %lu detected, coming from temporary track %lu of user %u", - finalMap, value, id); - myMeta.tracks.erase(finalMap); - // Set master to true before erasing the page, because we are responsible for cleaning up unused pages - updateMeta(); - eraseTrackDataPages(value); - nProxy.metaPages[finalMap].master = true; - nProxy.metaPages.erase(finalMap); - bufferLocations.erase(finalMap); - } - - // Register the new track as an active track. - activeTracks.insert(finalMap); - // Register the time of registration as initial value for the lastUpdated field, plus an extra 5 seconds just to be sure. - lastUpdated[finalMap] = Util::bootSecs() + 5; - // Register the user thats is pushing this element - pushLocation[finalMap] = data; - // Initialize the metadata for this track if it was not in place yet. - if (!myMeta.tracks.count(finalMap)){ - DEBUG_MSG(DLVL_MEDIUM, "Inserting metadata for track number %" PRIu64, finalMap); - myMeta.tracks[finalMap] = trackMeta.tracks.begin()->second; - myMeta.tracks[finalMap].firstms = 0; - myMeta.tracks[finalMap].lastms = 0; - myMeta.tracks[finalMap].trackID = finalMap; - } - // Update the metadata to reflect all changes - updateMeta(); - // Write the final mapped track number and keyframe number to the user page element - // This is used to resume pushing as well as pushing new tracks - userConn.setTrackId(index, finalMap); - if (myMeta.tracks[finalMap].keys.size()){ - userConn.setKeynum(index, myMeta.tracks[finalMap].keys.rbegin()->getNumber()); - }else{ - userConn.setKeynum(index, 0); - } - } - // If the track is active, and this is the element responsible for pushing it - if (activeTracks.count(value) && pushLocation[value] == data){ - // Open the track index page if we dont have it open yet - if (!nProxy.metaPages.count(value) || !nProxy.metaPages[value].mapped){ - char firstPage[NAME_BUFFER_SIZE]; - snprintf(firstPage, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, config->getString("streamname").c_str(), value); - nProxy.metaPages[value].init(firstPage, SHM_TRACK_INDEX_SIZE, false, false); - } - if (nProxy.metaPages[value].mapped){ - // Update the metadata for this track - updateTrackMeta(value); - hasPush = true; - } - } + generatePids.clear(); + for (std::map::iterator it = runningProcs.begin(); it != runningProcs.end(); it++){ + generatePids.insert(it->second); } } - - void inputBuffer::updateTrackMeta(unsigned long tNum){ - // Store a reference for easier access - std::map &locations = bufferLocations[tNum]; - char *mappedPointer = nProxy.metaPages[tNum].mapped; - if (!mappedPointer){return;} - VERYHIGH_MSG("Updating meta for track %lu, %lu pages", tNum, locations.size()); - - // First detect all entries on metaPage - for (int i = 0; i < 8192; i += 8){ - char *tmpOffset = mappedPointer + i; - if (Bit::btohl(tmpOffset) == 0 && Bit::btohl(tmpOffset + 4) == 0){continue;} - unsigned long keyNum = Bit::btohl(tmpOffset); - - // Add an entry into bufferLocations[tNum] for the pages we haven't handled yet. - if (!locations.count(keyNum)){ - locations[keyNum].curOffset = 0; - VERYHIGH_MSG("Page %lu detected, with %" PRIu32 " keys", keyNum, Bit::btohl(tmpOffset + 4)); + void inputBuffer::userOnActive(size_t id){ + ///\todo Add tracing of earliest watched keys, to prevent data going out of memory for + /// still-watching viewers + if (users.getStatus(id) != COMM_STATUS_DISCONNECT && users.getStatus(id) & COMM_STATUS_SOURCE){ + sourcePids[users.getPid(id)].insert(users.getTrack(id)); + if (!M.trackValid(users.getTrack(id))){ + users.setStatus(COMM_STATUS_DISCONNECT, id); + return; } - locations[keyNum].pageNum = keyNum; - locations[keyNum].keyNum = Bit::btohl(tmpOffset + 4); + // GeneratePids holds the pids of the process that generate data, so ignore those for determining if a push is ingested. + if (!generatePids.count(users.getPid(id))){hasPush = true;} } - // Since the map is ordered by keynumber, this loop updates the metadata for each page from oldest to newest - for (std::map::iterator pageIt = locations.begin(); - pageIt != locations.end(); pageIt++){ - updateMetaFromPage(tNum, pageIt->first); + + if (!(users.getStatus(id) & COMM_STATUS_DONOTTRACK)){++connectedUsers;} + } + void inputBuffer::userOnDisconnect(size_t id){ + if (sourcePids.count(users.getPid(id)) && sourcePids[users.getPid(id)].count(users.getTrack(id))){ + INFO_MSG("Disconnected track %" PRIu32, users.getTrack(id)); + meta.refresh(); + removeTrack(users.getTrack(id)); + sourcePids[users.getPid(id)].erase(users.getTrack(id)); } } + void inputBuffer::userLeadOut(){ + /*LTS-START*/ + static std::set prevValidTracks; - void inputBuffer::updateMetaFromPage(unsigned long tNum, unsigned long pageNum){ - VERYHIGH_MSG("Updating meta for track %lu page %lu", tNum, pageNum); - DTSCPageData &pageData = bufferLocations[tNum][pageNum]; - - // If the current page is over its 8mb "splitting" boundary - if (pageData.curOffset > (8 * 1024 * 1024)){ - // And the last keyframe in the parsed metadata is further in the stream than this page - if (pageData.pageNum + pageData.keyNum < myMeta.tracks[tNum].keys.rbegin()->getNumber()){ - // Assume the entire page is already parsed - return; + std::set validTracks = M.getValidTracks(); + if (validTracks != prevValidTracks){ + prevValidTracks = validTracks; + if (Triggers::shouldTrigger("LIVE_TRACK_LIST")){ + JSON::Value triggerPayload; + M.toJSON(triggerPayload, true, true); + std::string payload = config->getString("streamname") + "\n" + triggerPayload.toString() + "\n"; + Triggers::doTrigger("LIVE_TRACK_LIST", payload, config->getString("streamname")); } } + /*LTS-END*/ + } - // Otherwise open and parse the page - - // Open the page if it is not yet open - if (!nProxy.curPageNum.count(tNum) || nProxy.curPageNum[tNum] != pageNum || - !nProxy.curPage[tNum].mapped){ - // DO NOT ERASE THE PAGE HERE, master is not set to true - nProxy.curPageNum.erase(tNum); - char nextPageName[NAME_BUFFER_SIZE]; - snprintf(nextPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, - config->getString("streamname").c_str(), tNum, pageNum); - nProxy.curPage[tNum].init(nextPageName, 20971520); - // If the page can not be opened, stop here - if (!nProxy.curPage[tNum].mapped){ - WARN_MSG("Could not open page: %s", nextPageName); - return; - } - nProxy.curPageNum[tNum] = pageNum; - } - - DTSC::Packet tmpPack; - if (!nProxy.curPage[tNum].mapped[pageData.curOffset]){ - VERYHIGH_MSG("No packet on page %lu for track %lu, waiting...", pageNum, tNum); - return; - } - tmpPack.reInit(nProxy.curPage[tNum].mapped + pageData.curOffset, 0); - // No new data has been written on the page since last update - if (!tmpPack){return;} - lastUpdated[tNum] = Util::bootSecs(); - while (tmpPack){ - // Make sure the first item on a page is always marked as key frame - myMeta.nextIsKey = !pageData.curOffset; - // Update the metadata with this packet - myMeta.update(tmpPack, segmentSize); /*LTS*/ - // Set the first time when appropriate - if (pageData.firstTime == 0){pageData.firstTime = tmpPack.getTime();} - // Update the offset on the page with the size of the current packet - pageData.curOffset += tmpPack.getDataLen(); - // Attempt to read in the next packet - tmpPack.reInit(nProxy.curPage[tNum].mapped + pageData.curOffset, 0); - } + uint64_t inputBuffer::retrieveSetting(DTSC::Scan &streamCfg, const std::string &setting, + const std::string &option){ + std::string opt = (option == "" ? setting : option); + // If stream is not configured, use commandline option + if (!streamCfg){return config->getOption(opt).asInt();} + // If it is configured, and the setting is present, use it always + if (streamCfg.getMember(setting)){return streamCfg.getMember(setting).asInt();} + // If configured, but setting not present, fall back to default + return config->getOption(opt, true)[0u].asInt(); } bool inputBuffer::preRun(){ @@ -1031,63 +549,28 @@ namespace Mist{ std::string strName = config->getString("streamname"); Util::sanitizeName(strName); strName = strName.substr(0, (strName.find_first_of("+ "))); - char tmpBuf[NAME_BUFFER_SIZE]; snprintf(tmpBuf, NAME_BUFFER_SIZE, SHM_STREAM_CONF, strName.c_str()); Util::DTSCShmReader rStrmConf(tmpBuf); DTSC::Scan streamCfg = rStrmConf.getScan(); - long long tmpNum; - // if stream is configured and setting is present, use it, always - if (streamCfg && streamCfg.getMember("DVR")){ - tmpNum = streamCfg.getMember("DVR").asInt(); - }else{ - if (streamCfg){ - // otherwise, if stream is configured use the default - tmpNum = config->getOption("bufferTime", true)[0u].asInt(); - }else{ - // if not, use the commandline argument - tmpNum = config->getOption("bufferTime").asInt(); - } - } + uint64_t tmpNum = retrieveSetting(streamCfg, "DVR", "bufferTime"); if (tmpNum < 1000){tmpNum = 1000;} // if the new value is different, print a message and apply it if (bufferTime != tmpNum){ - DEBUG_MSG(DLVL_DEVEL, "Setting bufferTime from %u to new value of %lli", bufferTime, tmpNum); + DEVEL_MSG("Setting bufferTime from %" PRIu64 " to new value of %" PRIu64, bufferTime, tmpNum); bufferTime = tmpNum; } /*LTS-START*/ - // if stream is configured and setting is present, use it, always - if (streamCfg && streamCfg.getMember("cut")){ - tmpNum = streamCfg.getMember("cut").asInt(); - }else{ - if (streamCfg){ - // otherwise, if stream is configured use the default - tmpNum = config->getOption("cut", true)[0u].asInt(); - }else{ - // if not, use the commandline argument - tmpNum = config->getOption("cut").asInt(); - } - } + tmpNum = retrieveSetting(streamCfg, "cut"); // if the new value is different, print a message and apply it if (cutTime != tmpNum){ - INFO_MSG("Setting cutTime from %u to new value of %lli", cutTime, tmpNum); + INFO_MSG("Setting cutTime from %" PRIu64 " to new value of %" PRIu64, cutTime, tmpNum); cutTime = tmpNum; } - // if stream is configured and setting is present, use it, always - if (streamCfg && streamCfg.getMember("resume")){ - tmpNum = streamCfg.getMember("resume").asInt(); - }else{ - if (streamCfg){ - // otherwise, if stream is configured use the default - tmpNum = config->getOption("resume", true)[0u].asInt(); - }else{ - // if not, use the commandline argument - tmpNum = config->getOption("resume").asInt(); - } - } + tmpNum = retrieveSetting(streamCfg, "resume"); // if the new value is different, print a message and apply it if (resumeMode != (bool)tmpNum){ INFO_MSG("Setting resume mode from %s to new value of %s", @@ -1095,23 +578,15 @@ namespace Mist{ resumeMode = tmpNum; } - // if stream is configured and setting is present, use it, always - if (streamCfg && streamCfg.getMember("segmentsize")){ - tmpNum = streamCfg.getMember("segmentsize").asInt(); - }else{ - if (streamCfg){ - // otherwise, if stream is configured use the default - tmpNum = config->getOption("segmentsize", true)[0u].asInt(); - }else{ - // if not, use the commandline argument - tmpNum = config->getOption("segmentsize").asInt(); - } - } - if (tmpNum < myMeta.biggestFragment() / 2){tmpNum = myMeta.biggestFragment() / 2;} + tmpNum = retrieveSetting(streamCfg, "segmentsize"); + if (M && tmpNum < M.biggestFragment() / 2){tmpNum = M.biggestFragment() / 2;} // if the new value is different, print a message and apply it if (segmentSize != tmpNum){ - INFO_MSG("Setting segmentSize from %u to new value of %lli", segmentSize, tmpNum); + INFO_MSG("Setting segmentSize from %" PRIu64 " to new value of %" PRIu64, segmentSize, tmpNum); segmentSize = tmpNum; + if (M && M.getMinimumFragmentDuration() == 0){ + meta.setMinimumFragmentDuration(segmentSize); + } } if (streamCfg){ JSON::Value configuredProcesses = streamCfg.getMember("processes").asJSON(); @@ -1122,6 +597,11 @@ namespace Mist{ } uint64_t inputBuffer::findTrack(const std::string &trackVal){ + std::set validTracks = M.getValidTracks(); + if (!validTracks.size()){ + return INVALID_TRACK_ID; + }// No tracks == we don't have a valid + // track if (!trackVal.size() || trackVal == "0"){return 0;}// don't select anything in particular if (trackVal.find(',') != std::string::npos){ // Comma-separated list, recurse. @@ -1131,54 +611,89 @@ namespace Mist{ uint64_t r = findTrack(item); if (r){return r;}// return first match } - return 0; // nothing found + return INVALID_TRACK_ID; // nothing found } - size_t trackNo = JSON::Value(trackVal).asInt(); + uint64_t trackNo = JSON::Value(trackVal).asInt(); if (trackVal == JSON::Value(trackNo).asString()){ // It's an integer number - if (!myMeta.tracks.count(trackNo)){return 0;} + if (!validTracks.count(trackNo)){ + return INVALID_TRACK_ID; // nothing found + } return trackNo; } std::string trackLow = trackVal; Util::stringToLower(trackLow); if (trackLow == "all" || trackLow == "*"){ // select all tracks of this type - if (!myMeta.tracks.size()){return 0;} - return myMeta.tracks.begin()->first; + return *validTracks.begin(); } // attempt to do language/codec matching // convert 2-character language codes into 3-character language codes if (trackLow.size() == 2){trackLow = Encodings::ISO639::twoToThree(trackLow);} - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - const DTSC::Track &Trk = it->second; - std::string codecLow = Trk.codec; + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + std::string codecLow = M.getCodec(*it); Util::stringToLower(codecLow); - if (Trk.lang == trackLow || trackLow == codecLow){return it->first;} + if (M.getLang(*it) == trackLow || trackLow == codecLow){return *it;} } - return 0; + return INVALID_TRACK_ID; // nothing found } /*LTS-START*/ /// Checks if all processes are running, starts them if needed, stops them if needed void inputBuffer::checkProcesses(const JSON::Value &procs){ + if (!M.getValidTracks().size()){return;} std::set newProcs; + std::map wouldSelect; // used for building args int zero = 0; int out = fileno(stdout); int err = fileno(stderr); - char *argarr[2]; // approx max # of args (with a wide margin) + char *argarr[3]; // Convert to strings jsonForEachConst(procs, it){ JSON::Value tmp = *it; tmp["source"] = streamName; - if (tmp.isMember("source_track") && findTrack(tmp["source_track"].asString()) == 0){ - // No match - skip this process + if (!M.getValidTracks().size() && + (!tmp.isMember("source_track") && !tmp.isMember("track_select"))){ continue; } + if (tmp.isMember("source_track")){ + std::string sourceTrack = tmp["source_track"].asString(); + if (sourceTrack != "null" && findTrack(sourceTrack) == INVALID_TRACK_ID){ + // No match - skip this process + continue; + } + } + std::stringstream s; + if (tmp.isMember("track_select")){ + std::set wouldSelect = Util::wouldSelect(M, tmp["track_select"].asStringRef()); + if (!wouldSelect.size()){ + // No match - skip this process + continue; + } + for (std::set::iterator it = wouldSelect.begin(); it != wouldSelect.end(); it++){ + s << *it << " "; + } + } + if (tmp.isMember("track_inhibit")){ + std::set wouldSelect = Util::wouldSelect( + M, std::string("audio=none&video=none&subtitle=none&") + tmp["track_inhibit"].asStringRef()); + if (wouldSelect.size()){ + // Inhibit if there is a match and we're not already running. + if (!runningProcs.count(tmp.toString())){continue;} + bool inhibited = false; + std::set myTracks = M.getMySourceTracks(runningProcs[tmp.toString()]); + // Also inhibit if there is a match with not-the-currently-running-process + for (std::set::iterator it = wouldSelect.begin(); it != wouldSelect.end(); ++it){ + if (!myTracks.count(*it)){inhibited = true;} + } + if (inhibited){continue;} + } + } newProcs.insert(tmp.toString()); + wouldSelect[tmp.toString()] = s.str(); } // shut down deleted/changed processes @@ -1187,7 +702,7 @@ namespace Mist{ for (it = runningProcs.begin(); it != runningProcs.end(); it++){ if (!newProcs.count(it->first)){ if (Util::Procs::isActive(it->second)){ - INFO_MSG("Stopping process %llu: %s", it->second, it->first.c_str()); + INFO_MSG("Stopping process %d: %s", it->second, it->first.c_str()); Util::Procs::Stop(it->second); } runningProcs.erase(it); @@ -1207,6 +722,7 @@ namespace Mist{ argarr[1] = (char *)config.c_str(); argarr[2] = 0; INFO_MSG("Starting process: %s %s", argarr[0], argarr[1]); + INFO_MSG(" WouldSelect is %s", wouldSelect.at(*newProcs.begin()).c_str()); runningProcs[*newProcs.begin()] = Util::Procs::StartPiped(argarr, &zero, &out, &err); } newProcs.erase(newProcs.begin()); diff --git a/src/input/input_buffer.h b/src/input/input_buffer.h index 3fbbac07..6811465b 100644 --- a/src/input/input_buffer.h +++ b/src/input/input_buffer.h @@ -1,6 +1,5 @@ -#include - #include "input.h" +#include #include #include @@ -12,11 +11,12 @@ namespace Mist{ void onCrash(); private: - void fillBufferDetails(JSON::Value &details); - unsigned int bufferTime; - unsigned int cutTime; - unsigned int segmentSize; /*LTS*/ - unsigned int lastReTime; /*LTS*/ + void fillBufferDetails(JSON::Value &details) const; + uint64_t bufferTime; + uint64_t cutTime; + size_t segmentSize; /*LTS*/ + uint64_t lastReTime; /*LTS*/ + uint64_t finalMillis; bool hasPush; bool resumeMode; IPC::semaphore *liveMeta; @@ -28,29 +28,30 @@ namespace Mist{ void updateMeta(); bool readHeader(){return false;} bool needHeader(){return false;} - void getNext(bool smart = true){} - void updateTrackMeta(unsigned long tNum); - void updateMetaFromPage(unsigned long tNum, unsigned long pageNum); - void seek(int seekTime){} - void trackSelect(std::string trackSpec){} - bool removeKey(unsigned int tid); + void getNext(size_t idx = INVALID_TRACK_ID){}; + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){}; + + void removeTrack(size_t tid); + + bool removeKey(size_t tid); void removeUnused(); - void eraseTrackDataPages(unsigned long tid); void finish(); - void userCallback(char *data, size_t len, unsigned int id); - std::set negotiatingTracks; - std::set activeTracks; - std::map lastUpdated; - std::map negotiationTimeout; - /// Maps trackid to a pagenum->pageData map - std::map > bufferLocations; - std::map pushLocation; - inputBuffer *singleton; + + uint64_t retrieveSetting(DTSC::Scan &streamCfg, const std::string &setting, const std::string &option = ""); + + void userLeadIn(); + void userOnActive(size_t id); + void userOnDisconnect(size_t id); + void userLeadOut(); // This is used for an ugly fix to prevent metadata from disappearing in some cases. - std::map initData; + std::map initData; + uint64_t findTrack(const std::string &trackVal); void checkProcesses(const JSON::Value &procs); // LTS std::map runningProcs; // LTS + + std::set generatePids; + std::map > sourcePids; }; }// namespace Mist diff --git a/src/input/input_dtsc.cpp b/src/input/input_dtsc.cpp index cf2ec767..e13581f8 100644 --- a/src/input/input_dtsc.cpp +++ b/src/input/input_dtsc.cpp @@ -60,10 +60,19 @@ namespace Mist{ capa["optional"]["segmentsize"]["type"] = "uint"; capa["optional"]["segmentsize"]["default"] = 1900; /*LTS-END*/ + + F = NULL; + lockCache = false; + lockNeeded = false; } 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, @@ -129,37 +138,40 @@ namespace Mist{ void inputDTSC::parseStreamHeader(){ while (srcConn.connected() && config->is_active){ srcConn.spool(); - 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::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{ + if (!srcConn.Received().available(8)){ 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 validTracks = M.getMySourceTracks(getpid()); + userSelect.clear(); + for (std::set::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();} bool inputDTSC::checkArguments(){ - if (!needsLock()){ - return true; - }else{ - if (!config->getString("streamname").size()){ - if (config->getString("output") == "-"){ - std::cerr << "Output to stdout not yet supported" << std::endl; - return false; - } - }else{ - if (config->getString("output") != "-"){ - std::cerr << "File output in player mode not supported" << std::endl; - return false; - } + if (!needsLock()){return true;} + if (!config->getString("streamname").size()){ + if (config->getString("output") == "-"){ + std::cerr << "Output to stdout not yet supported" << std::endl; + return false; + } + }else{ + if (config->getString("output") != "-"){ + 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; } @@ -222,120 +235,215 @@ namespace Mist{ } bool inputDTSC::readHeader(){ - if (!inFile){return false;} - if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0){ - DEBUG_MSG(DLVL_FAIL, "Missing external header file"); - return false; + if (!F){return false;} + if (!readExistingHeader()){ + size_t moreHeader = 0; + 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 true; + + return meta; } - void inputDTSC::getNext(bool smart){ + void inputDTSC::getNext(size_t idx){ if (!needsLock()){ - thisPacket.reInit(srcConn); - while (config->is_active){ - if (thisPacket.getVersion() == DTSC::DTCM){ - nProxy.userClient.keepAlive(); - std::string cmd; - thisPacket.getString("cmd", cmd); - if (cmd == "reset"){ - // Read next packet - thisPacket.reInit(srcConn); - if (thisPacket.getVersion() == DTSC::DTSC_HEAD){ - DTSC::Meta newMeta; - newMeta.reinit(thisPacket); - // Detect new tracks - std::set newTracks; - for (std::map::iterator it = newMeta.tracks.begin(); - it != newMeta.tracks.end(); it++){ - if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);} - } - - for (std::set::iterator it = newTracks.begin(); it != newTracks.end(); it++){ - INFO_MSG("Reset: adding track %d", *it); - myMeta.tracks[*it] = newMeta.tracks[*it]; - continueNegotiate(*it, true); - } - - // Detect removed tracks - std::set deletedTracks; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (!newMeta.tracks.count(it->first)){deletedTracks.insert(it->first);} - } - - for (std::set::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 newTracks; - for (std::map::iterator it = newMeta.tracks.begin(); - it != newMeta.tracks.end(); it++){ - if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);} - } - - for (std::set::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 + getNextFromStream(idx); + return; + } + if (!currentPositions.size()){ + WARN_MSG("No seek positions set - returning empty packet."); + thisPacket.null(); + return; + } + seekPos thisPos = *currentPositions.begin(); + fseek(F, thisPos.bytePos, SEEK_SET); + if (feof(F)){ + thisPacket.null(); + return; + } + clearerr(F); + currentPositions.erase(currentPositions.begin()); + lastreadpos = ftell(F); + if (fread(buffer, 4, 1, F) != 1){ + if (feof(F)){ + INFO_MSG("End of file reached while seeking @ %" PRIu64, lastreadpos); + }else{ + ERROR_MSG("Could not seek to next @ %" PRIu64, lastreadpos); } + 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{ - if (smart){ - inFile.seekNext(); - }else{ - inFile.parseNext(); + std::set tracks = M.getValidTracks(); + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++){ + seekNext(seekTime, *it, true); } - thisPacket = inFile.getPacket(); } } - void inputDTSC::seek(int seekTime){ - inFile.seek_time(seekTime); - initialTime = 0; - playUntil = 0; - } - - void inputDTSC::trackSelect(std::string trackSpec){ - selectedTracks.clear(); - long long unsigned int 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); + void inputDTSC::seekNext(uint64_t ms, size_t trackIdx, bool forceSeek){ + seekPos tmpPos; + tmpPos.trackID = trackIdx; + if (!forceSeek && thisPacket && ms >= thisPacket.getTime() && trackIdx >= thisPacket.getTrackId()){ + tmpPos.seekTime = thisPacket.getTime(); + tmpPos.bytePos = ftell(F); + }else{ + tmpPos.seekTime = 0; + tmpPos.bytePos = 0; + } + if (feof(F)){ + clearerr(F); + fseek(F, 0, SEEK_SET); + 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{ - 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 diff --git a/src/input/input_dtsc.h b/src/input/input_dtsc.h index 9fec463f..aa7d4afb 100644 --- a/src/input/input_dtsc.h +++ b/src/input/input_dtsc.h @@ -1,7 +1,27 @@ #include "input.h" + +#include +#include //for FILE + #include 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{ public: inputDTSC(Util::Config *cfg); @@ -15,13 +35,24 @@ namespace Mist{ bool checkArguments(); bool readHeader(); bool needHeader(); - void getNext(bool smart = true); - void seek(int seekTime); - void trackSelect(std::string trackSpec); + void getNext(size_t idx = INVALID_TRACK_ID); + void getNextFromStream(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); - DTSC::File inFile; + FILE *F; Socket::Connection srcConn; + + bool lockCache; + bool lockNeeded; + + std::set currentPositions; + + uint64_t lastreadpos; + + char buffer[8]; + + void seekNext(uint64_t ms, size_t trackIdx, bool forceSeek = false); }; }// namespace Mist diff --git a/src/input/input_ebml.cpp b/src/input/input_ebml.cpp index 170688f0..ebb07a59 100644 --- a/src/input/input_ebml.cpp +++ b/src/input/input_ebml.cpp @@ -41,6 +41,7 @@ namespace Mist{ lastClusterTime = 0; bufferedPacks = 0; wantBlocks = true; + totalBytes = 0; } 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); while (ptr.size() < needed){ 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 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; } + totalBytes += toRead; ptr.size() = needed; needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal); if (ptr.size() >= needed){ @@ -141,26 +144,26 @@ namespace Mist{ 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){ lastClusterTime = E.getValUInt(); - DONTEVEN_MSG("Cluster time %llu ms", lastClusterTime); + DONTEVEN_MSG("Cluster time %" PRIu64 " ms", lastClusterTime); } return true; } bool InputEBML::readExistingHeader(){ if (!Input::readExistingHeader()){return false;} - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); ++it){ - if (it->second.codec == "PCMLE"){ - it->second.codec = "PCM"; - swapEndianness.insert(it->first); + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (M.getCodec(*it) == "PCMLE"){ + meta.setCodec(*it, "PCM"); + swapEndianness.insert(*it); } } - if (myMeta.inputLocalVars.isMember("timescale")){ - timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0; + if (M.inputLocalVars.isMember("timescale")){ + timeScale = ((double)M.inputLocalVars["timescale"].asInt()) / 1000000.0; } return true; } @@ -169,6 +172,7 @@ namespace Mist{ if (!inFile){return false;} // Create header file from file uint64_t bench = Util::getMicros(); + if (!meta){meta.reInit(streamName);} while (readElement()){ EBML::Element E(ptr, readingMinimal); @@ -178,7 +182,7 @@ namespace Mist{ ERROR_MSG("Track without track number encountered, ignoring"); continue; } - uint64_t trackNo = tmpElem.getValUInt(); + uint64_t trackID = tmpElem.getValUInt(); tmpElem = E.findChild(EBML::EID_CODECID); if (!tmpElem){ ERROR_MSG("Track without codec id encountered, ignoring"); @@ -311,32 +315,33 @@ namespace Mist{ } tmpElem = E.findChild(EBML::EID_LANGUAGE); if (tmpElem){lang = tmpElem.getValString();} - DTSC::Track &Trk = myMeta.tracks[trackNo]; - Trk.trackID = trackNo; - Trk.lang = lang; - Trk.codec = trueCodec; - Trk.type = trueType; - Trk.init = init; - if (Trk.type == "video"){ + size_t idx = M.trackIDToIndex(trackID, getpid()); + if (idx == INVALID_TRACK_ID){idx = meta.addTrack();} + meta.setID(idx, trackID); + meta.setLang(idx, lang); + meta.setCodec(idx, trueCodec); + meta.setType(idx, trueType); + meta.setInit(idx, init); + if (trueType == "video"){ 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); - Trk.height = tmpElem ? tmpElem.getValUInt() : 0; - Trk.fpks = 0; + meta.setHeight(idx, tmpElem ? tmpElem.getValUInt() : 0); + meta.setFpks(idx, 0); } - if (Trk.type == "audio"){ + if (trueType == "audio"){ 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); - Trk.size = tmpElem ? tmpElem.getValUInt() : 0; + meta.setSize(idx, tmpElem ? tmpElem.getValUInt() : 0); 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){ uint64_t timeScaleVal = E.getValUInt(); - myMeta.inputLocalVars["timescale"] = timeScaleVal; + meta.inputLocalVars["timescale"] = timeScaleVal; timeScale = ((double)timeScaleVal) / 1000000.0; } // 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 newTime = lastClusterTime + B.getTimecode(); trackPredictor &TP = packBuf[tNum]; - DTSC::Track &Trk = myMeta.tracks[tNum]; - bool isVideo = (Trk.type == "video"); - bool isAudio = (Trk.type == "audio"); - bool isASS = (Trk.codec == "subtitle" && Trk.init.size()); + size_t idx = meta.trackIDToIndex(tNum, getpid()); + bool isVideo = (M.getType(idx) == "video"); + bool isAudio = (M.getType(idx) == "audio"); + bool isASS = (M.getCodec(idx) == "subtitle" && M.getInit(idx).size()); // If this is a new video keyframe, flush the corresponding trackPredictor if (isVideo && B.isKeyframe()){ while (TP.hasPackets(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.flush(); } for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){ if (frameNo){ - if (Trk.codec == "AAC"){ - newTime += (1000000 / Trk.rate) / timeScale; // assume ~1000 samples per frame - }else if (Trk.codec == "MP3"){ - newTime += (1152000 / Trk.rate) / timeScale; // 1152 samples per frame - }else if (Trk.codec == "DTS"){ + if (M.getCodec(idx) == "AAC"){ + newTime += (1000000 / M.getRate(idx)) / timeScale; // assume ~1000 samples per frame + }else if (M.getCodec(idx) == "MP3"){ + newTime += (1152000 / M.getRate(idx)) / timeScale; // 1152 samples per frame + }else if (M.getCodec(idx) == "DTS"){ // Assume 512 samples per frame (DVD default) // actual amount can be calculated from data, but data // is not available during header generation... // See: http://www.stnsoft.com/DVD/dtshdr.html - newTime += (512000 / Trk.rate) / timeScale; + newTime += (512000 / M.getRate(idx)) / timeScale; }else{ 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); @@ -388,7 +394,7 @@ namespace Mist{ } while (TP.hasPackets()){ 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(); } } @@ -398,23 +404,25 @@ namespace Mist{ for (std::map::iterator it = packBuf.begin(); it != packBuf.end(); ++it){ trackPredictor &TP = it->second; while (TP.hasPackets(true)){ - packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video"); - myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key); + packetData &C = + 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(); } } } bench = Util::getMicros(bench); - INFO_MSG("Header generated in %llu ms", bench / 1000); + INFO_MSG("Header generated in %" PRIu64 " ms", bench / 1000); clearPredictors(); bufferedPacks = 0; - myMeta.toFile(config->getString("input") + ".dtsh"); - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); ++it){ - if (it->second.codec == "PCMLE"){ - it->second.codec = "PCM"; - swapEndianness.insert(it->first); + M.toFile(config->getString("input") + ".dtsh"); + + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (M.getCodec(*it) == "PCMLE"){ + meta.setCodec(*it, "PCM"); + swapEndianness.insert(*it); } } return true; @@ -422,7 +430,7 @@ namespace Mist{ void InputEBML::fillPacket(packetData &C){ if (swapEndianness.count(C.track)){ - switch (myMeta.tracks[C.track].size){ + switch (M.getSize(M.trackIDToIndex(C.track, getpid()))){ case 16:{ char *ptr = C.ptr; uint32_t ptrSize = C.dsize; @@ -455,16 +463,18 @@ namespace Mist{ }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 if (bufferedPacks && packBuf.size()){ for (std::map::iterator it = packBuf.begin(); it != packBuf.end(); ++it){ trackPredictor &TP = it->second; 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); TP.remove(); --bufferedPacks; @@ -481,7 +491,7 @@ namespace Mist{ for (std::map::iterator it = packBuf.begin(); it != packBuf.end(); ++it){ trackPredictor &TP = it->second; 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); TP.remove(); --bufferedPacks; @@ -494,7 +504,8 @@ namespace Mist{ return; } 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{ B = EBML::Block(ptr); } @@ -502,10 +513,10 @@ namespace Mist{ uint64_t tNum = B.getTrackNum(); uint64_t newTime = lastClusterTime + B.getTimecode(); trackPredictor &TP = packBuf[tNum]; - DTSC::Track &Trk = myMeta.tracks[tNum]; - bool isVideo = (Trk.type == "video"); - bool isAudio = (Trk.type == "audio"); - bool isASS = (Trk.codec == "subtitle" && Trk.init.size()); + size_t trackIdx = M.trackIDToIndex(tNum, getpid()); + bool isVideo = (M.getType(trackIdx) == "video"); + bool isAudio = (M.getType(trackIdx) == "audio"); + bool isASS = (M.getCodec(trackIdx) == "subtitle" && M.getInit(trackIdx).size()); // If this is a new video keyframe, flush the corresponding trackPredictor if (isVideo && B.isKeyframe() && bufferedPacks){ @@ -523,18 +534,19 @@ namespace Mist{ for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){ if (frameNo){ - if (Trk.codec == "AAC"){ - newTime += (1000000 / Trk.rate) / timeScale; // assume ~1000 samples per frame - }else if (Trk.codec == "MP3"){ - newTime += (1152000 / Trk.rate) / timeScale; // 1152 samples per frame - }else if (Trk.codec == "DTS"){ + if (M.getCodec(trackIdx) == "AAC"){ + newTime += (1000000 / M.getRate(trackIdx)) / timeScale; // assume ~1000 samples per frame + }else if (M.getCodec(trackIdx) == "MP3"){ + newTime += (1152000 / M.getRate(trackIdx)) / timeScale; // 1152 samples per frame + }else if (M.getCodec(trackIdx) == "DTS"){ // Assume 512 samples per frame (DVD default) // actual amount can be calculated from data, but data // is not available during header generation... // See: http://www.stnsoft.com/DVD/dtshdr.html - newTime += (512000 / Trk.rate) / timeScale; + newTime += (512000 / M.getRate(trackIdx)) / timeScale; }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); @@ -560,22 +572,26 @@ namespace Mist{ }else{ // We didn't set thisPacket yet. Read another. // 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; clearPredictors(); bufferedPacks = 0; uint64_t mainTrack = getMainSelectedTrack(); - DTSC::Track Trk = myMeta.tracks[mainTrack]; - bool isVideo = (Trk.type == "video"); - uint64_t seekPos = Trk.keys[0].getBpos(); + + DTSC::Keys keys(M.keys(mainTrack)); + DTSC::Parts parts(M.parts(mainTrack)); + uint64_t seekPos = keys.getBpos(0); // Replay the parts of the previous keyframe, so the timestaps match up - for (unsigned int i = 1; i < Trk.keys.size(); i++){ - if (Trk.keys[i].getTime() > seekTime){break;} - seekPos = Trk.keys[i].getBpos(); + uint64_t partCount = 0; + for (size_t i = 0; i < keys.getEndValid(); i++){ + 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); } diff --git a/src/input/input_ebml.h b/src/input/input_ebml.h index dea7a898..f3fc28c0 100644 --- a/src/input/input_ebml.h +++ b/src/input/input_ebml.h @@ -11,14 +11,7 @@ namespace Mist{ uint64_t time, offset, track, dsize, bpos; bool key; Util::ResizeablePointer ptr; - packetData(){ - time = 0; - offset = 0; - track = 0; - dsize = 0; - bpos = 0; - key = false; - } + packetData() : 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, uint64_t packBytePos, bool isKeyframe, void *dataPtr = 0){ time = packTime; @@ -132,10 +125,12 @@ namespace Mist{ p.offset = ((uint32_t)((frameOffset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame; } lastTime = p.time; - INSANE_MSG("Outputting%s %llu+%llu (#%llu, Max=%llu), display at %llu", (p.key ? "KEY" : ""), - p.time, p.offset, rem, maxOffset, p.time + p.offset); + INSANE_MSG("Outputting%s %" PRIu64 "+%" PRIu64 " (#%" PRIu64 ", Max=%" PRIu64 + "), display at %" PRIu64, + (p.key ? "KEY" : ""), p.time, p.offset, rem, maxOffset, p.time + p.offset); return p; } + void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, bool isVideo, void *dataPtr = 0){ if (!ctr){lowestTime = packTime;} @@ -155,13 +150,16 @@ namespace Mist{ bool needsLock(); protected: + virtual size_t streamByteCount(){ + return totalBytes; + }; // For live streams: to update the stats with correct values. void fillPacket(packetData &C); bool checkArguments(); bool preRun(); bool readHeader(); bool readElement(); - void getNext(bool smart = true); - void seek(int seekTime); + void getNext(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); void clearPredictors(); FILE *inFile; Util::ResizeablePointer ptr; @@ -177,6 +175,7 @@ namespace Mist{ bool needHeader(){return needsLock() && !readExistingHeader();} double timeScale; bool wantBlocks; + size_t totalBytes; }; }// namespace Mist diff --git a/src/input/input_flv.cpp b/src/input/input_flv.cpp index 0d62342f..5ac9157a 100644 --- a/src/input/input_flv.cpp +++ b/src/input/input_flv.cpp @@ -28,6 +28,8 @@ namespace Mist{ capa["codecs"][0u][1u].append("MP3"); } + inputFLV::~inputFLV(){} + bool inputFLV::checkArguments(){ if (config->getString("input") == "-"){ std::cerr << "Input from stdin not yet supported" << std::endl; @@ -77,45 +79,49 @@ namespace Mist{ bool inputFLV::readHeader(){ if (!inFile){return false;} + meta.reInit(config->getString("streamname")); // Create header file from FLV data Util::fseek(inFile, 13, SEEK_SET); AMF::Object amf_storage; - long long int lastBytePos = 13; + uint64_t lastBytePos = 13; uint64_t bench = Util::getMicros(); while (!feof(inFile) && !FLV::Parse_Error){ if (tmpTag.FileLoader(inFile)){ - tmpTag.toMeta(myMeta, amf_storage); + tmpTag.toMeta(meta, amf_storage); if (!tmpTag.getDataLen()){continue;} if (tmpTag.needsInitData() && tmpTag.isInitData()){continue;} - myMeta.update(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getDataLen(), - lastBytePos, tmpTag.isKeyframe); + size_t tNumber = meta.trackIDToIndex(tmpTag.getTrackID(), getpid()); + if (tNumber != INVALID_TRACK_ID){ + meta.update(tmpTag.tagTime(), tmpTag.offset(), tNumber, tmpTag.getDataLen(), lastBytePos, + tmpTag.isKeyframe); + } lastBytePos = Util::ftell(inFile); } } bench = Util::getMicros(bench); - INFO_MSG("Header generated in %llu ms: @%lld, %s, %s", bench / 1000, lastBytePos, - myMeta.vod ? "VoD" : "NOVoD", myMeta.live ? "Live" : "NOLive"); + INFO_MSG("Header generated in %" PRIu64 " ms: @%" PRIu64 ", %s, %s", bench / 1000, lastBytePos, + M.getVod() ? "VoD" : "NOVoD", M.getLive() ? "Live" : "NOLive"); if (FLV::Parse_Error){ tmpTag = FLV::Tag(); 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); return true; } - void inputFLV::getNext(bool smart){ - long long int lastBytePos = Util::ftell(inFile); - if (selectedTracks.size() == 1){ + void inputFLV::getNext(size_t idx){ + uint64_t lastBytePos = Util::ftell(inFile); + if (idx != INVALID_TRACK_ID){ uint8_t targetTag = 0x08; - if (selectedTracks.count(1)){targetTag = 0x09;} - if (selectedTracks.count(3)){targetTag = 0x12;} + if (M.getType(idx) == "video"){targetTag = 0x09;} + if (M.getType(idx) == "meta"){targetTag = 0x12;} FLV::seekToTagType(inFile, targetTag); } while (!feof(inFile) && !FLV::Parse_Error){ if (tmpTag.FileLoader(inFile)){ - if (!selectedTracks.count(tmpTag.getTrackID())){ + if (idx != INVALID_TRACK_ID && M.getID(idx) != tmpTag.getTrackID()){ lastBytePos = Util::ftell(inFile); continue; } @@ -129,22 +135,22 @@ namespace Mist{ if (FLV::Parse_Error){ FLV::Parse_Error = false; 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(); return; } if (!tmpTag.getDataLen() || (tmpTag.needsInitData() && tmpTag.isInitData())){ - return getNext(); + return getNext(idx); } - thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getData(), - tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); // init packet from tmpTags data + size_t tNumber = meta.trackIDToIndex(tmpTag.getTrackID(), getpid()); + thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tNumber, tmpTag.getData(), + tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); - DTSC::Track &trk = myMeta.tracks[tmpTag.getTrackID()]; - if (trk.codec == "PCM" && trk.size == 16){ + if (M.getCodec(idx) == "PCM" && M.getSize(idx) == 16){ char *ptr = 0; size_t ptrSize = 0; 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]; ptr[i] = ptr[i + 1]; 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 // keyframe. Flv files are never multi-track, so track 1 is video, track 2 is audio. - int trackSeek = (selectedTracks.count(1) ? 1 : 2); - uint64_t seekPos = myMeta.tracks[trackSeek].keys[0].getBpos(); - for (unsigned int i = 0; i < myMeta.tracks[trackSeek].keys.size(); i++){ - if (myMeta.tracks[trackSeek].keys[i].getTime() > seekTime){break;} - 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 = ""; - } - } + size_t seekTrack = (idx == INVALID_TRACK_ID ? M.mainTrack() : idx); + DTSC::Keys keys(M.keys(seekTrack)); + uint32_t keyNum = keys.getNumForTime(seekTime); + Util::fseek(inFile, keys.getBpos(keyNum), SEEK_SET); } }// namespace Mist diff --git a/src/input/input_flv.h b/src/input/input_flv.h index bd4283f5..2ece2444 100644 --- a/src/input/input_flv.h +++ b/src/input/input_flv.h @@ -6,15 +6,15 @@ namespace Mist{ class inputFLV : public Input{ public: inputFLV(Util::Config *cfg); + ~inputFLV(); protected: // Private Functions bool checkArguments(); bool preRun(); bool readHeader(); - void getNext(bool smart = true); - void seek(int seekTime); - void trackSelect(std::string trackSpec); + void getNext(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); bool keepRunning(); FLV::Tag tmpTag; uint64_t lastModTime; diff --git a/src/input/input_folder.h b/src/input/input_folder.h index 9f8f5b2e..7500f788 100644 --- a/src/input/input_folder.h +++ b/src/input/input_folder.h @@ -11,6 +11,8 @@ namespace Mist{ bool checkArguments(){return false;}; bool readHeader(){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 diff --git a/src/input/input_h264.cpp b/src/input/input_h264.cpp index 66460879..7b410e88 100644 --- a/src/input/input_h264.cpp +++ b/src/input/input_h264.cpp @@ -17,10 +17,9 @@ namespace Mist{ inputProcess = 0; } - bool InputH264::preRun(){ + bool InputH264::openStreamSource(){ if (config->getString("input") != "-"){ std::string input = config->getString("input"); - const char *argv[2]; input = input.substr(10); char *args[128]; @@ -50,15 +49,20 @@ namespace Mist{ myConn.open(fileno(stdout), fileno(stdin)); } 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; } + 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(){ std::string input = config->getString("input"); if (input != "-" && input.substr(0, 10) != "h264-exec:"){ @@ -68,7 +72,7 @@ namespace Mist{ return true; } - void InputH264::getNext(bool smart){ + void InputH264::getNext(size_t idx){ do{ if (!myConn.spool()){ Util::sleep(25); @@ -87,18 +91,18 @@ namespace Mist{ while (nalSize && NAL.data()[nalSize - 1] == 0){--nalSize;} if (!nalSize){continue;} 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){spsInfo = 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::SPSMeta spsChar = sps.getCharacteristics(); - myMeta.tracks[1].width = spsChar.width; - myMeta.tracks[1].height = spsChar.height; - myMeta.tracks[1].fpks = spsChar.fps * 1000; - if (myMeta.tracks[1].fpks < 100 || myMeta.tracks[1].fpks > 1000000){ - myMeta.tracks[1].fpks = 0; + meta.setWidth(tNumber, spsChar.width); + meta.setHeight(tNumber, spsChar.height); + meta.setFpks(tNumber, spsChar.fps * 1000); + if (M.getFpks(tNumber) < 100 || M.getFpks(tNumber) > 1000000){ + meta.setFpks(tNumber, 0); } MP4::AVCC avccBox; avccBox.setVersion(1); @@ -109,14 +113,14 @@ namespace Mist{ avccBox.setSPS(spsInfo); avccBox.setPPSCount(1); avccBox.setPPS(ppsInfo); - myMeta.tracks[1].init = std::string(avccBox.payload(), avccBox.payloadSize()); + meta.setInit(tNumber, avccBox.payload(), avccBox.payloadSize()); } continue; } - if (myMeta.tracks[1].init.size()){ + if (M.getInit(tNumber).size()){ uint64_t ts = Util::bootMS() - startTime; - if (myMeta.tracks[1].fpks){ts = frameCount * (1000000 / myMeta.tracks[1].fpks);} - thisPacket.genericFill(ts, 0, 1, 0, 0, 0, h264::isKeyframe(NAL.data(), nalSize)); + if (M.getFpks(tNumber)){ts = frameCount * (1000000 / M.getFpks(tNumber));} + thisPacket.genericFill(ts, 0, tNumber, 0, 0, 0, h264::isKeyframe(NAL.data(), nalSize)); thisPacket.appendNal(NAL.data(), nalSize); ++frameCount; return; diff --git a/src/input/input_h264.h b/src/input/input_h264.h index 23736f9b..207468a2 100644 --- a/src/input/input_h264.h +++ b/src/input/input_h264.h @@ -8,24 +8,24 @@ namespace Mist{ InputH264(Util::Config *cfg); protected: + virtual bool needHeader(){return false;} bool checkArguments(); - bool preRun(); - void getNext(bool smart = true); + void getNext(size_t idx = INVALID_TRACK_ID); Socket::Connection myConn; std::string ppsInfo; std::string spsInfo; uint64_t frameCount; // Empty defaults bool readHeader(){return true;} - bool openStreamSource(){return true;} + bool openStreamSource(); void closeStreamSource(){} - void parseStreamHeader(){} - void seek(int seekTime){} - void trackSelect(std::string trackSpec){} + void parseStreamHeader(); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){} bool needsLock(){return false;} uint64_t startTime; pid_t inputProcess; uint32_t waitsSinceData; + size_t tNumber; }; }// namespace Mist diff --git a/src/input/input_hls.cpp b/src/input/input_hls.cpp index 3084cffd..ae8a0f43 100644 --- a/src/input/input_hls.cpp +++ b/src/input/input_hls.cpp @@ -118,7 +118,7 @@ namespace Mist{ /// Called by the global callbackFunc, to prevent timeouts bool inputHLS::callback(){ - if (nProxy.userClient.isAlive()){nProxy.userClient.keepAlive();} + keepAlive(); return config->is_active; } @@ -415,7 +415,7 @@ namespace Mist{ if (key == "TARGETDURATION"){ 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());} @@ -520,12 +520,13 @@ namespace Mist{ memset(entry.keyAES, 0, 16); } - if (!isUrl()){ - std::ifstream fileSource; - std::string test = root.link(entry.filename).getFilePath(); - 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));} - totalBytes += fileSource.tellg(); + if (!isUrl()){ + std::ifstream fileSource; + std::string test = root.link(entry.filename).getFilePath(); + 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));} + entry.byteEnd = fileSource.tellg(); + totalBytes += entry.byteEnd; } entry.timestamp = lastTimestamp + startTime; @@ -588,20 +589,6 @@ namespace Mist{ 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(){ if (!initPlaylist(config->getString("input"))){ FAIL_MSG("Failed to load HLS playlist, aborting"); @@ -668,6 +655,8 @@ namespace Mist{ }while (!segDowner.atEnd()); if (preCounter < counter){break;}// We're done reading this playlist! } + + in.close(); } tsStream.clear(); currentPlaylist = 0; @@ -681,13 +670,12 @@ namespace Mist{ bool hasHeader = false; // See whether a separate header file exists. - DTSC::File tmp(config->getString("input") + ".dtsh"); - if (tmp){ - myMeta = tmp.getMeta(); - if (myMeta){hasHeader = true;} - } + meta.reInit(config->getString("streamname"), config->getString("input") + ".dtsh"); + hasHeader = (bool)M; - 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 @@ -728,19 +716,22 @@ namespace Mist{ counter++; } - if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){ - tsStream.initializeMetadata(myMeta, tmpTrackId, packetId); + size_t idx = M.trackIDToIndex(packetId, getpid()); + INFO_MSG("PacketID: %" PRIu64 ", pid: %d, mapped to %zu", packetId, getpid(), idx); + if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){ + tsStream.initializeMetadata(meta, tmpTrackId, packetId); + INFO_MSG("InitializingMeta for track %zu -> %zu", tmpTrackId, packetId); + idx = M.trackIDToIndex(packetId, getpid()); } if (!hasHeader){ headerPack.getString("data", data, dataLen); - uint64_t pBPos = headerPack.getInt("bpos"); // keyframe data exists, so always add 19 bytes keyframedata. - long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0; - long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11; - myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId, - headerPack.hasMember("keyframe"), packSendSize); + uint32_t packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0; + size_t packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11; + meta.update(headerPack.getTime(), packOffset, idx, dataLen, entId, + headerPack.hasMember("keyframe"), packSendSize); } } @@ -766,19 +757,18 @@ namespace Mist{ counter++; } - if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){ - tsStream.initializeMetadata(myMeta, tmpTrackId, packetId); + if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){ + tsStream.initializeMetadata(meta, tmpTrackId, packetId); + idx = M.trackIDToIndex(packetId, getpid()); } if (!hasHeader){ headerPack.getString("data", data, dataLen); - uint64_t pBPos = headerPack.getInt("bpos"); - // keyframe data exists, so always add 19 bytes keyframedata. long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0; long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11; - myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId, - headerPack.hasMember("keyframe"), packSendSize); + meta.update(headerPack.getTime(), packOffset, idx, dataLen, entId, + headerPack.hasMember("keyframe"), packSendSize); } tsStream.getEarliestPacket(headerPack); } @@ -790,10 +780,8 @@ namespace Mist{ if (streamIsLive){return true;} INFO_MSG("write header file..."); - std::ofstream oFile((config->getString("input") + ".dtsh").c_str()); - - oFile << myMeta.toJSON().toNetPacked(); - oFile.close(); + M.toFile((config->getString("input") + ".dtsh").c_str()); + in.close(); return true; } @@ -802,34 +790,30 @@ namespace Mist{ bool inputHLS::openStreamSource(){return true;} - void inputHLS::getNext(bool smart){ + void inputHLS::getNext(size_t idx){ INSANE_MSG("Getting next"); uint32_t tid = 0; bool finished = false; - if (selectedTracks.size()){tid = *selectedTracks.begin();} + if (userSelect.size()){tid = userSelect.begin()->first;} thisPacket.null(); - while (config->is_active && (needsLock() || nProxy.userClient.isAlive())){ + while (config->is_active && (needsLock() || keepAlive())){ // Check if we have a packet bool hasPacket = false; if (streamIsLive){ hasPacket = tsStream.hasPacketOnEachTrack() || (segDowner.atEnd() && tsStream.hasPacket()); }else{ - hasPacket = tsStream.hasPacket(getMappedTrackId(tid)); + hasPacket = tsStream.hasPacket(M.getID(idx) & 0xFFFF); } // Yes? Excellent! Read and return it. if (hasPacket){ // Read - if (myMeta.live){ + if (M.getLive()){ tsStream.getEarliestPacket(thisPacket); - tid = getOriginalTrackId(currentPlaylist, thisPacket.getTrackId()); - if (!tid){ - INFO_MSG("Track %" PRIu64 " on PLS %u -> %" PRIu32, thisPacket.getTrackId(), currentPlaylist, tid); - continue; - } + tid = M.trackIDToIndex((((uint64_t)currentPlaylist) << 16) + thisPacket.getTrackId(), getpid()); }else{ - tsStream.getPacket(getMappedTrackId(tid), thisPacket); + tsStream.getPacket(M.getID(idx) & 0xFFFF, thisPacket); } if (!thisPacket){ FAIL_MSG("Could not getNext TS packet!"); @@ -940,25 +924,19 @@ namespace Mist{ } // Note: bpos is overloaded here for playlist entry! - void inputHLS::seek(int seekTime){ + void inputHLS::seek(uint64_t seekTime, size_t idx){ plsTimeOffset.clear(); plsLastTime.clear(); plsInterval.clear(); tsStream.clear(); - int trackId = 0; + uint64_t trackId = M.getID(idx); - unsigned long plistEntry = 0xFFFFFFFFull; - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - unsigned long thisBPos = 0; - for (std::deque::iterator keyIt = myMeta.tracks[*it].keys.begin(); - keyIt != myMeta.tracks[*it].keys.end(); keyIt++){ - if (keyIt->getTime() > seekTime){break;} - thisBPos = keyIt->getBpos(); - } - if (thisBPos < plistEntry){ - plistEntry = thisBPos; - trackId = *it; - } + unsigned long plistEntry = 0; + + DTSC::Keys keys(M.keys(idx)); + for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); i++){ + if (keys.getTime(i) > seekTime){break;} + plistEntry = keys.getBpos(i); } 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;} tthread::lock_guard guard(entryMutex); 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 /// by timestamp. /// 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. - if (selectedTracks.size() == 1){return getMappedTrackPlaylist(*selectedTracks.begin());} + if (userSelect.size() == 1){return ((M.getID(userSelect.begin()->first) >> 16) & 0xFFFF);} uint64_t firstTimeStamp = 0; int tmpId = -1; int segCount = 0; diff --git a/src/input/input_hls.h b/src/input/input_hls.h index f813d7cc..9d328132 100644 --- a/src/input/input_hls.h +++ b/src/input/input_hls.h @@ -27,8 +27,8 @@ namespace Mist{ uint64_t bytePos; uint64_t mUTC; ///< UTC unix millis timestamp of first packet, if known float duration; - unsigned int timestamp; - unsigned int wait; + uint64_t timestamp; + uint64_t wait; char ivec[16]; char keyAES[16]; }; @@ -75,10 +75,10 @@ namespace Mist{ int noChangeCount; uint64_t lastFileIndex; - int waitTime; + uint64_t waitTime; PlaylistType playlistType; - unsigned int lastTimestamp; - unsigned int startTime; + uint64_t lastTimestamp; + uint64_t startTime; uint64_t nextUTC; ///< If non-zero, the UTC timestamp of the next segment on this playlist char keyAES[16]; std::map keys; @@ -103,7 +103,7 @@ namespace Mist{ int version; int targetDuration; bool endPlaylist; - int currentPlaylist; + uint64_t currentPlaylist; bool allowRemap; ///< True if the next packet may remap the timestamps bool allowSoftRemap; ///< True if the next packet may soft-remap the timestamps @@ -113,7 +113,7 @@ namespace Mist{ std::map plsLastTime; std::map plsInterval; - int currentIndex; + size_t currentIndex; std::string currentFile; TS::Stream tsStream; ///< Used for parsing the incoming ts stream @@ -128,9 +128,9 @@ namespace Mist{ bool preSetup(); bool readHeader(); bool needHeader(){return true;} - void getNext(bool smart = true); - void seek(int seekTime); - void trackSelect(std::string trackSpec); + void getNext(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); + FILE *inFile; FILE *tsFile; @@ -141,10 +141,7 @@ namespace Mist{ void parseStreamHeader(); - uint32_t getMappedTrackId(uint64_t id); - uint32_t getMappedTrackPlaylist(uint64_t id); - uint64_t getOriginalTrackId(uint32_t playlistId, uint32_t id); - int getEntryId(int playlistId, uint64_t bytePos); + size_t getEntryId(uint32_t playlistId, uint64_t bytePos); }; }// namespace Mist diff --git a/src/input/input_ismv.cpp b/src/input/input_ismv.cpp index a0bd1765..3acea387 100644 --- a/src/input/input_ismv.cpp +++ b/src/input/input_ismv.cpp @@ -40,297 +40,210 @@ namespace Mist{ } return true; } + bool inputISMV::preRun(){ - // open File inFile = fopen(config->getString("input").c_str(), "r"); - if (!inFile){return false;} - return true; + return inFile; // True if not null } bool inputISMV::readHeader(){ if (!inFile){return false;} + meta.reInit(streamName); // parse ismv header fseek(inFile, 0, SEEK_SET); - std::string ftyp; - readBox("ftyp", ftyp); - if (ftyp == ""){return false;} - std::string boxRes; - readBox("moov", boxRes); - if (boxRes == ""){return false;} - MP4::MOOV hdrBox; - hdrBox.read(boxRes); - parseMoov(hdrBox); - int tId; - std::vector trunSamples; - std::vector initVecs; - std::string mdat; - unsigned int currOffset; - JSON::Value lastPack; - unsigned int lastBytePos = 0; - std::map currentDuration; - unsigned int curBytePos = ftell(inFile); + // Skip mandatory ftyp box + MP4::skipBox(inFile); + + MP4::MOOV moovBox; + moovBox.read(inFile); + parseMoov(moovBox); + + std::map duration; + + uint64_t currOffset; + uint64_t lastBytePos = 0; + uint64_t curBytePos = ftell(inFile); // parse fragments form here - while (parseFrag(tId, trunSamples, initVecs, mdat)){ - if (!currentDuration.count(tId)){currentDuration[tId] = 0;} + + size_t tId; + std::vector trunSamples; + + while (readMoofSkipMdat(tId, trunSamples) && !feof(inFile)){ + if (!duration.count(tId)){duration[tId] = 0;} currOffset = 8; - int i = 0; - while (currOffset < mdat.size()){ - lastPack.null(); - lastPack["time"] = currentDuration[tId] / 10000; - lastPack["trackid"] = tId; - lastPack["data"] = mdat.substr(currOffset, trunSamples[i].sampleSize); - if (initVecs.size() == trunSamples.size()){lastPack["ivec"] = initVecs[i];} - lastPack["duration"] = trunSamples[i].sampleDuration; - if (myMeta.tracks[tId].type == "video"){ - if (i){ - lastBytePos++; - }else{ - lastPack["keyframe"] = 1; - lastBytePos = curBytePos; - } - lastPack["bpos"] = lastBytePos; - unsigned int offsetConv = trunSamples[i].sampleOffset / 10000; - lastPack["offset"] = (int)offsetConv; + for (std::vector::iterator it = trunSamples.begin(); + it != trunSamples.end(); it++){ + bool first = (it == trunSamples.begin()); + + int64_t offsetConv = 0; + if (M.getType(tId) == "video"){offsetConv = it->sampleOffset / 10000;} + + if (first){ + lastBytePos = curBytePos; }else{ - if (i == 0){ - lastPack["keyframe"] = 1; - lastPack["bpos"] = curBytePos; - } + ++lastBytePos; } - myMeta.update(lastPack); - currentDuration[tId] += trunSamples[i].sampleDuration; - currOffset += trunSamples[i].sampleSize; - i++; + + meta.update(duration[tId] / 10000, offsetConv, tId, it->sampleSize, lastBytePos, first); + duration[tId] += it->sampleDuration; + currOffset += it->sampleSize; } curBytePos = ftell(inFile); } - myMeta.toFile(config->getString("input") + ".dtsh"); + M.toFile(config->getString("input") + ".dtsh"); return true; } - void inputISMV::getNext(bool smart){ - static JSON::Value thisPack; - thisPack.null(); - if (!buffered.size()){ - thisPacket.null(); - return; - } - int tId = buffered.begin()->trackId; - thisPack["time"] = (uint64_t)(buffered.begin()->time / 10000); - thisPack["trackid"] = tId; - fseek(inFile, buffered.begin()->position, SEEK_SET); - char *tmpData = (char *)malloc(buffered.begin()->size * sizeof(char)); - fread(tmpData, buffered.begin()->size, 1, inFile); - thisPack["data"] = std::string(tmpData, buffered.begin()->size); - free(tmpData); - if (buffered.begin()->iVec != ""){thisPack["ivec"] = buffered.begin()->iVec;} - if (myMeta.tracks[tId].type == "video"){ - if (buffered.begin()->isKeyFrame){thisPack["keyframe"] = 1;} - thisPack["offset"] = (uint64_t)(buffered.begin()->offset / 10000); - }else{ - if (buffered.begin()->isKeyFrame){thisPack["keyframe"] = 1;} - } - thisPack["bpos"] = (uint64_t)buffered.begin()->position; + void inputISMV::getNext(size_t idx){ + thisPacket.null(); + + if (!buffered.size()){return;} + + seekPos thisPos = *buffered.begin(); buffered.erase(buffered.begin()); - if (buffered.size() < 2 * selectedTracks.size()){ - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - parseFragHeader(*it, lastKeyNum[*it]); - lastKeyNum[*it]++; + + fseek(inFile, thisPos.position, SEEK_SET); + dataPointer.allocate(thisPos.size); + fread(dataPointer, thisPos.size, 1, inFile); + + thisPacket.genericFill(thisPos.time / 10000, thisPos.offset / 10000, thisPos.trackId, + dataPointer, thisPos.size, 0, thisPos.isKeyFrame); + + if (buffered.size() < 2 * (idx == INVALID_TRACK_ID ? M.getValidTracks().size() : 1)){ + std::set validTracks = M.getValidTracks(); + if (idx != INVALID_TRACK_ID){ + validTracks.clear(); + validTracks.insert(idx); + } + + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + bufferFragmentData(*it, ++lastKeyNum[*it]); } } - std::string tmpStr = thisPack.toNetPacked(); - thisPacket.reInit(tmpStr.data(), tmpStr.size()); + if (idx != INVALID_TRACK_ID && thisPacket.getTrackId() != M.getID(idx)){getNext(idx);} } - ///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number - bool inputISMV::atKeyFrame(){return thisPacket.getFlag("keyframe");} - - void inputISMV::seek(int seekTime){ + void inputISMV::seek(uint64_t seekTime, size_t idx){ buffered.clear(); - // Seek to corresponding keyframes on EACH track - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - unsigned int i; - for (i = 0; i < myMeta.tracks[*it].keys.size(); i++){ - if (myMeta.tracks[*it].keys[i].getTime() > seekTime && i > 0){// Ehh, whut? - break; - } - } - i--; - DEBUG_MSG(DLVL_DEVEL, "ISMV seek frag %d:%d", *it, i); - parseFragHeader(*it, i); - lastKeyNum[*it] = i + 1; - } - } + lastKeyNum.clear(); - void inputISMV::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 = ""; - } + // Select tracks + std::set validTracks = M.getValidTracks(); + if (idx != INVALID_TRACK_ID){ + validTracks.clear(); + validTracks.insert(idx); + } + + // For each selected track + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); ++it){ + DTSC::Keys keys(M.keys(*it)); + uint32_t i; + for (i = keys.getFirstValid(); i < keys.getEndValid(); i++){ + if (keys.getTime(i) >= seekTime){break;} + } + INFO_MSG("ISMV seek frag %zu:%" PRIu32, *it, i); + bufferFragmentData(*it, i); + lastKeyNum[*it] = i; } - seek(0); } void inputISMV::parseMoov(MP4::MOOV &moovBox){ - for (unsigned int i = 0; i < moovBox.getContentCount(); i++){ - if (moovBox.getContent(i).isType("mvhd")){ - MP4::MVHD content = (MP4::MVHD &)moovBox.getContent(i); - } - if (moovBox.getContent(i).isType("trak")){ - MP4::TRAK content = (MP4::TRAK &)moovBox.getContent(i); - int trackId; - for (unsigned int j = 0; j < content.getContentCount(); j++){ - if (content.getContent(j).isType("tkhd")){ - MP4::TKHD subContent = (MP4::TKHD &)content.getContent(j); - trackId = subContent.getTrackID(); - myMeta.tracks[trackId].trackID = trackId; - } - if (content.getContent(j).isType("mdia")){ - MP4::MDIA subContent = (MP4::MDIA &)content.getContent(j); - for (unsigned int k = 0; k < subContent.getContentCount(); k++){ - if (subContent.getContent(k).isType("hdlr")){ - MP4::HDLR subsubContent = (MP4::HDLR &)subContent.getContent(k); - if (subsubContent.getHandlerType() == "soun"){ - myMeta.tracks[trackId].type = "audio"; - } - if (subsubContent.getHandlerType() == "vide"){ - myMeta.tracks[trackId].type = "video"; - } - } - if (subContent.getContent(k).isType("minf")){ - MP4::MINF subsubContent = (MP4::MINF &)subContent.getContent(k); - for (unsigned int l = 0; l < subsubContent.getContentCount(); l++){ - if (subsubContent.getContent(l).isType("stbl")){ - MP4::STBL stblBox = (MP4::STBL &)subsubContent.getContent(l); - for (unsigned int m = 0; m < stblBox.getContentCount(); m++){ - if (stblBox.getContent(m).isType("stsd")){ - MP4::STSD stsdBox = (MP4::STSD &)stblBox.getContent(m); - for (unsigned int n = 0; n < stsdBox.getEntryCount(); n++){ - if (stsdBox.getEntry(n).isType("mp4a") || - stsdBox.getEntry(n).isType("enca")){ - MP4::MP4A mp4aBox = (MP4::MP4A &)stsdBox.getEntry(n); - myMeta.tracks[trackId].codec = "AAC"; - std::string tmpStr; - tmpStr += (char)((mp4aBox.toAACInit() & 0xFF00) >> 8); - tmpStr += (char)(mp4aBox.toAACInit() & 0x00FF); - myMeta.tracks[trackId].init = tmpStr; - myMeta.tracks[trackId].channels = mp4aBox.getChannelCount(); - myMeta.tracks[trackId].size = mp4aBox.getSampleSize(); - myMeta.tracks[trackId].rate = mp4aBox.getSampleRate(); - } - if (stsdBox.getEntry(n).isType("avc1") || - stsdBox.getEntry(n).isType("encv")){ - MP4::AVC1 avc1Box = (MP4::AVC1 &)stsdBox.getEntry(n); - myMeta.tracks[trackId].height = avc1Box.getHeight(); - myMeta.tracks[trackId].width = avc1Box.getWidth(); - myMeta.tracks[trackId].init = - std::string(avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize()); - myMeta.tracks[trackId].codec = "H264"; - } - } - } - } - } - } - } - } - } + std::deque trak = moovBox.getChildren(); + for (std::deque::iterator it = trak.begin(); it != trak.end(); it++){ + size_t tNumber = meta.addTrack(); + + meta.setID(tNumber, it->getChild().getTrackID()); + + MP4::MDIA mdia = it->getChild(); + + MP4::HDLR hdlr = mdia.getChild(); + if (hdlr.getHandlerType() == "soun"){meta.setType(tNumber, "audio");} + if (hdlr.getHandlerType() == "vide"){meta.setType(tNumber, "video");} + + MP4::STSD stsd = mdia.getChild().getChild().getChild(); + for (size_t i = 0; i < stsd.getEntryCount(); ++i){ + if (stsd.getEntry(i).isType("mp4a") || stsd.getEntry(i).isType("enca")){ + MP4::MP4A mp4aBox = (MP4::MP4A &)stsd.getEntry(i); + std::string tmpStr; + tmpStr += (char)((mp4aBox.toAACInit() & 0xFF00) >> 8); + tmpStr += (char)(mp4aBox.toAACInit() & 0x00FF); + meta.setCodec(tNumber, "AAC"); + meta.setInit(tNumber, tmpStr); + meta.setChannels(tNumber, mp4aBox.getChannelCount()); + meta.setSize(tNumber, mp4aBox.getSampleSize()); + meta.setRate(tNumber, mp4aBox.getSampleRate()); + } + if (stsd.getEntry(i).isType("avc1") || stsd.getEntry(i).isType("encv")){ + MP4::AVC1 avc1Box = (MP4::AVC1 &)stsd.getEntry(i); + meta.setCodec(tNumber, "H264"); + meta.setInit(tNumber, avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize()); + meta.setHeight(tNumber, avc1Box.getHeight()); + meta.setWidth(tNumber, avc1Box.getWidth()); } } } } - bool inputISMV::parseFrag(int &tId, std::vector &trunSamples, - std::vector &initVecs, std::string &mdat){ - tId = -1; + bool inputISMV::readMoofSkipMdat(size_t &tId, std::vector &trunSamples){ + tId = INVALID_TRACK_ID; trunSamples.clear(); - initVecs.clear(); - mdat.clear(); - std::string boxRes; - readBox("moof", boxRes); - if (boxRes == ""){return false;} + MP4::MOOF moof; - moof.read(boxRes); - for (unsigned int i = 0; i < moof.getContentCount(); i++){ - if (moof.getContent(i).isType("traf")){ - MP4::TRAF trafBox = (MP4::TRAF &)moof.getContent(i); - for (unsigned int j = 0; j < trafBox.getContentCount(); j++){ - if (trafBox.getContent(j).isType("trun")){ - MP4::TRUN trunBox = (MP4::TRUN &)trafBox.getContent(j); - for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){ - trunSamples.push_back(trunBox.getSampleInformation(i)); - } - } - if (trafBox.getContent(j).isType("tfhd")){ - tId = ((MP4::TFHD &)trafBox.getContent(j)).getTrackID(); - } - /*LTS-START*/ - if (trafBox.getContent(j).isType("uuid")){ - if (((MP4::UUID &)trafBox.getContent(j)).getUUID() == - "a2394f52-5a9b-4f14-a244-6c427c648df4"){ - MP4::UUID_SampleEncryption uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j); - for (unsigned int i = 0; i < uuidBox.getSampleCount(); i++){ - initVecs.push_back(uuidBox.getSample(i).InitializationVector); - } - } - } - /*LTS-END*/ + moof.read(inFile); + + if (feof(inFile)){return false;} + + MP4::TRAF trafBox = moof.getChild(); + for (size_t j = 0; j < trafBox.getContentCount(); j++){ + if (trafBox.getContent(j).isType("trun")){ + MP4::TRUN trunBox = (MP4::TRUN &)trafBox.getContent(j); + for (size_t i = 0; i < trunBox.getSampleInformationCount(); i++){ + trunSamples.push_back(trunBox.getSampleInformation(i)); } } + if (trafBox.getContent(j).isType("tfhd")){ + tId = M.trackIDToIndex(((MP4::TFHD &)trafBox.getContent(j)).getTrackID(), getpid()); + } } - readBox("mdat", mdat); - if (mdat == ""){return false;} - return true; + + MP4::skipBox(inFile); + return !feof(inFile); } - void inputISMV::parseFragHeader(const unsigned int &trackId, const unsigned int &keyNum){ - if (!myMeta.tracks.count(trackId) || (myMeta.tracks[trackId].keys.size() <= keyNum)){return;} - long long int lastPos = myMeta.tracks[trackId].keys[keyNum].getBpos(); - long long int lastTime = myMeta.tracks[trackId].keys[keyNum].getTime() * 10000; - fseek(inFile, lastPos, SEEK_SET); - std::string boxRes; - readBox("moof", boxRes); - if (boxRes == ""){return;} - MP4::MOOF moof; - moof.read(boxRes); + void inputISMV::bufferFragmentData(size_t trackId, uint32_t keyNum){ + INFO_MSG("Bpos seek for %zu/%" PRIu32, trackId, keyNum); + if (trackId == INVALID_TRACK_ID){return;} + DTSC::Keys keys(M.keys(trackId)); + INFO_MSG("Key %" PRIu32 " / %zu", keyNum, keys.getEndValid()); + if (keyNum >= keys.getEndValid()){return;} + uint64_t currentPosition = keys.getBpos(keyNum); + uint64_t currentTime = keys.getTime(keyNum) * 10000; + INFO_MSG("Bpos seek to %" PRIu64, currentPosition); + fseek(inFile, currentPosition, SEEK_SET); + + MP4::MOOF moofBox; + moofBox.read(inFile); + + MP4::TRAF trafBox = moofBox.getChild(); + MP4::TRUN trunBox; - MP4::UUID_SampleEncryption uuidBox; /*LTS*/ - for (unsigned int i = 0; i < moof.getContentCount(); i++){ - if (moof.getContent(i).isType("traf")){ - MP4::TRAF trafBox = (MP4::TRAF &)moof.getContent(i); - for (unsigned int j = 0; j < trafBox.getContentCount(); j++){ - if (trafBox.getContent(j).isType("trun")){ - trunBox = (MP4::TRUN &)trafBox.getContent(j); - } - if (trafBox.getContent(j).isType("tfhd")){ - if (trackId != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){ - DEBUG_MSG(DLVL_FAIL, "Trackids do not match"); - return; - } - } - /*LTS-START*/ - if (trafBox.getContent(j).isType("uuid")){ - if (((MP4::UUID &)trafBox.getContent(j)).getUUID() == - "a2394f52-5a9b-4f14-a244-6c427c648df4"){ - uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j); - } - } - /*LTS-END*/ + MP4::UUID_SampleEncryption uuidBox; + for (unsigned int j = 0; j < trafBox.getContentCount(); j++){ + if (trafBox.getContent(j).isType("trun")){trunBox = (MP4::TRUN &)trafBox.getContent(j);} + if (trafBox.getContent(j).isType("tfhd")){ + if (M.getID(trackId) != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){ + FAIL_MSG("Trackids do not match"); + return; } } } - lastPos = ftell(inFile) + 8; + + currentPosition = ftell(inFile) + 8; for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){ seekPos myPos; - myPos.position = lastPos; + myPos.position = currentPosition; myPos.trackId = trackId; - myPos.time = lastTime; + myPos.time = currentTime; myPos.duration = trunBox.getSampleInformation(i).sampleDuration; myPos.size = trunBox.getSampleInformation(i).sampleSize; if (trunBox.getFlags() & MP4::trunsampleOffsets){ @@ -340,29 +253,9 @@ namespace Mist{ myPos.offset = 0; } myPos.isKeyFrame = (i == 0); - /*LTS-START*/ - if (i <= uuidBox.getSampleCount()){myPos.iVec = uuidBox.getSample(i).InitializationVector;} - /*LTS-END*/ - lastTime += trunBox.getSampleInformation(i).sampleDuration; - lastPos += trunBox.getSampleInformation(i).sampleSize; + currentTime += trunBox.getSampleInformation(i).sampleDuration; + currentPosition += trunBox.getSampleInformation(i).sampleSize; buffered.insert(myPos); } } - - void inputISMV::readBox(const char *type, std::string &result){ - int pos = ftell(inFile); - char mp4Head[8]; - fread(mp4Head, 8, 1, inFile); - fseek(inFile, pos, SEEK_SET); - if (memcmp(mp4Head + 4, type, 4)){ - DEBUG_MSG(DLVL_FAIL, "No %.4s box found at position %d", type, pos); - result = ""; - return; - } - unsigned int boxSize = (mp4Head[0] << 24) + (mp4Head[1] << 16) + (mp4Head[2] << 8) + mp4Head[3]; - char *tmpBox = (char *)malloc(boxSize * sizeof(char)); - fread(tmpBox, boxSize, 1, inFile); - result = std::string(tmpBox, boxSize); - free(tmpBox); - } }// namespace Mist diff --git a/src/input/input_ismv.h b/src/input/input_ismv.h index e7726328..5f62e83c 100644 --- a/src/input/input_ismv.h +++ b/src/input/input_ismv.h @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace Mist{ @@ -11,12 +12,12 @@ namespace Mist{ if (time < rhs.time){return true;} return (time == rhs.time && trackId < rhs.trackId); } - long long int position; - int trackId; - long long int time; - long long int duration; - int size; - long long int offset; + uint64_t position; + size_t trackId; + uint64_t time; + uint64_t duration; + uint64_t size; + int64_t offset; bool isKeyFrame; std::string iVec; }; @@ -30,20 +31,19 @@ namespace Mist{ bool checkArguments(); bool preRun(); bool readHeader(); - void getNext(bool smart = true); - void seek(int seekTime); - void trackSelect(std::string trackSpec); - bool atKeyFrame(); + virtual void getNext(size_t idx = INVALID_TRACK_ID); + virtual void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); FILE *inFile; void parseMoov(MP4::MOOV &moovBox); - bool parseFrag(int &tId, std::vector &trunSamples, - std::vector &initVecs, std::string &mdat); - void parseFragHeader(const unsigned int &trackId, const unsigned int &keyNum); - void readBox(const char *type, std::string &result); + bool readMoofSkipMdat(size_t &tId, std::vector &trunSamples); + + void bufferFragmentData(size_t trackId, uint32_t keyNum); std::set buffered; - std::map lastKeyNum; + std::map lastKeyNum; + + Util::ResizeablePointer dataPointer; }; }// namespace Mist diff --git a/src/input/input_mp3.cpp b/src/input/input_mp3.cpp index aa04e9a1..be1a6360 100644 --- a/src/input/input_mp3.cpp +++ b/src/input/input_mp3.cpp @@ -51,44 +51,45 @@ namespace Mist{ bool inputMP3::readHeader(){ if (!inFile){return false;} - myMeta = DTSC::Meta(); - myMeta.tracks[1].trackID = 1; - myMeta.tracks[1].type = "audio"; - myMeta.tracks[1].codec = "MP3"; + meta.reInit(config->getString("streamname")); + size_t tNum = meta.addTrack(); + meta.setID(tNum, tNum); + meta.setType(tNum, "audio"); + meta.setCodec(tNum, "MP3"); // Create header file from MP3 data char header[10]; fread(header, 10, 1, inFile); // Read a 10 byte header if (header[0] == 'I' || header[1] == 'D' || header[2] == '3'){ size_t id3size = (((int)header[6] & 0x7F) << 21) | (((int)header[7] & 0x7F) << 14) | (((int)header[8] & 0x7F) << 7) | - (header[9] & 0x7F) + 10 + ((header[5] & 0x10) ? 10 : 0); + ((header[9] & 0x7F) + 10 + ((header[5] & 0x10) ? 10 : 0)); INFO_MSG("id3 size: %lu bytes", id3size); fseek(inFile, id3size, SEEK_SET); }else{ fseek(inFile, 0, SEEK_SET); } // Read the first mp3 header for bitrate and such - size_t filePos = ftell(inFile); + uint64_t filePos = ftell(inFile); fread(header, 4, 1, inFile); fseek(inFile, filePos, SEEK_SET); Mpeg::MP2Info mp2Info = Mpeg::parseMP2Header(header); - myMeta.tracks[1].rate = mp2Info.sampleRate; - myMeta.tracks[1].channels = mp2Info.channels; + meta.setRate(tNum, mp2Info.sampleRate); + meta.setChannels(tNum, mp2Info.channels); getNext(); while (thisPacket){ - myMeta.update(thisPacket); + meta.update(thisPacket); getNext(); } fseek(inFile, 0, SEEK_SET); timestamp = 0; - myMeta.toFile(config->getString("input") + ".dtsh"); + M.toFile(config->getString("input") + ".dtsh"); return true; } - void inputMP3::getNext(bool smart){ + void inputMP3::getNext(size_t idx){ thisPacket.null(); static char packHeader[3000]; size_t filePos = ftell(inFile); @@ -107,7 +108,7 @@ namespace Mist{ } } if (!offset){ - DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %lu", filePos); + FAIL_MSG("Sync byte not found from offset %zu", filePos); return; } filePos += offset; @@ -141,34 +142,16 @@ namespace Mist{ fseek(inFile, filePos + dataSize, SEEK_SET); // Create a json value with the right data - static JSON::Value thisPack; - thisPack.null(); - thisPack["trackid"] = 1; - thisPack["bpos"] = (uint64_t)filePos; - thisPack["data"] = std::string(packHeader, dataSize); - thisPack["time"] = timestamp; - // Write the json value to lastpack - std::string tmpStr = thisPack.toNetPacked(); - thisPacket.reInit(tmpStr.data(), tmpStr.size()); + thisPacket.genericFill(timestamp, 0, idx, packHeader, dataSize, filePos, false); // Update the internal timestamp timestamp += (sampleCount / (sampleRate / 1000)); } - void inputMP3::seek(int seekTime){ - std::deque &keys = myMeta.tracks[1].keys; - size_t seekPos = keys[0].getBpos(); - for (unsigned int i = 0; i < keys.size(); i++){ - if (keys[i].getTime() > seekTime){break;} - seekPos = keys[i].getBpos(); - timestamp = keys[i].getTime(); - } - timestamp = seekTime; - fseek(inFile, seekPos, SEEK_SET); - } - - void inputMP3::trackSelect(std::string trackSpec){ - // Ignore, do nothing - // MP3 Always has only 1 track, so we can't select much else.. + void inputMP3::seek(uint64_t seekTime, size_t idx){ + DTSC::Keys keys(M.keys(idx)); + uint32_t keyNum = keys.getNumForTime(seekTime); + fseek(inFile, keys.getBpos(keyNum), SEEK_SET); + timestamp = keys.getTime(keyNum); } }// namespace Mist diff --git a/src/input/input_mp3.h b/src/input/input_mp3.h index d6790031..8048fe8d 100644 --- a/src/input/input_mp3.h +++ b/src/input/input_mp3.h @@ -21,9 +21,9 @@ namespace Mist{ bool checkArguments(); bool preRun(); bool readHeader(); - void getNext(bool smart = true); - void seek(int seekTime); - void trackSelect(std::string trackSpec); + void getNext(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); + double timestamp; FILE *inFile; diff --git a/src/input/input_mp4.cpp b/src/input/input_mp4.cpp index bb3ce326..b27a5943 100644 --- a/src/input/input_mp4.cpp +++ b/src/input/input_mp4.cpp @@ -32,6 +32,7 @@ namespace Mist{ stcoBox.clear(); co64Box.clear(); stco64 = false; + trackId = 0; } uint64_t mp4TrackHeader::size(){return (stszBox.asBox() ? stszBox.getSampleCount() : 0);} @@ -45,6 +46,7 @@ namespace Mist{ MP4::MDIA mdiaBox = trakBox.getChild(); timeScale = mdiaBox.getChild().getTimeScale(); + trackId = trakBox.getChild().getTrackID(); MP4::STBL stblBox = mdiaBox.getChild().getChild(); @@ -148,6 +150,14 @@ namespace Mist{ size = stszBox.getEntrySize(index); } + mp4TrackHeader &inputMP4::headerData(size_t trackID){ + static mp4TrackHeader none; + for (std::deque::iterator it = trackHeaders.begin(); it != trackHeaders.end(); it++){ + if (it->trackId == trackID){return *it;} + } + return none; + } + inputMP4::inputMP4(Util::Config *cfg) : Input(cfg){ malSize = 4; // initialise data read buffer to 0; data = (char *)malloc(malSize); @@ -200,9 +210,8 @@ namespace Mist{ return false; } - uint32_t trackNo = 0; - // first we get the necessary header parts + size_t tNumber = 0; while (!feof(inFile)){ std::string boxType = MP4::readBoxType(inFile); if (boxType == "erro"){break;} @@ -213,7 +222,8 @@ namespace Mist{ std::deque trak = moovBox.getChildren(); for (std::deque::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){ - headerData[++trackNo].read(*trakIt); + trackHeaders.push_back(mp4TrackHeader()); + trackHeaders.rbegin()->read(*trakIt); } continue; } @@ -228,7 +238,9 @@ namespace Mist{ if (readExistingHeader()){return true;} HIGH_MSG("Not read existing header"); - trackNo = 0; + meta.reInit(streamName); + + tNumber = 0; // Create header file from MP4 data while (!feof(inFile)){ std::string boxType = MP4::readBoxType(inFile); @@ -241,96 +253,95 @@ namespace Mist{ HIGH_MSG("Obtained %zu trak Boxes", trak.size()); for (std::deque::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){ - uint64_t trackNo = myMeta.tracks.size() + 1; - myMeta.tracks[trackNo].trackID = trackNo; - - MP4::TKHD tkhdBox = trakIt->getChild(); - if (tkhdBox.getWidth() > 0){ - myMeta.tracks[trackNo].width = tkhdBox.getWidth(); - myMeta.tracks[trackNo].height = tkhdBox.getHeight(); - } - MP4::MDIA mdiaBox = trakIt->getChild(); - MP4::MDHD mdhdBox = mdiaBox.getChild(); - uint64_t timescale = mdhdBox.getTimeScale(); - myMeta.tracks[trackNo].lang = mdhdBox.getLanguage(); - std::string hdlrType = mdiaBox.getChild().getHandlerType(); if (hdlrType != "vide" && hdlrType != "soun" && hdlrType != "sbtl"){ - headerData.erase(trackNo); - myMeta.tracks.erase(trackNo); - break; + INFO_MSG("Unsupported handler: %s", hdlrType.c_str()); + continue; } + tNumber = meta.addTrack(); + + MP4::TKHD tkhdBox = trakIt->getChild(); + if (tkhdBox.getWidth() > 0){ + meta.setWidth(tNumber, tkhdBox.getWidth()); + meta.setHeight(tNumber, tkhdBox.getHeight()); + } + meta.setID(tNumber, tkhdBox.getTrackID()); + + MP4::MDHD mdhdBox = mdiaBox.getChild(); + uint64_t timescale = mdhdBox.getTimeScale(); + meta.setLang(tNumber, mdhdBox.getLanguage()); + MP4::STBL stblBox = mdiaBox.getChild().getChild(); MP4::STSD stsdBox = stblBox.getChild(); MP4::Box sEntryBox = stsdBox.getEntry(0); std::string sType = sEntryBox.getType(); - HIGH_MSG("Found track %zu of type %s", trackNo, sType.c_str()); + HIGH_MSG("Found track %zu of type %s", tNumber, sType.c_str()); if (sType == "avc1" || sType == "h264" || sType == "mp4v"){ MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox; - myMeta.tracks[trackNo].type = "video"; - myMeta.tracks[trackNo].codec = "H264"; - - myMeta.tracks[trackNo].width = vEntryBox.getWidth(); - myMeta.tracks[trackNo].height = vEntryBox.getHeight(); - + meta.setType(tNumber, "video"); + meta.setCodec(tNumber, "H264"); + if (!meta.getWidth(tNumber)){ + meta.setWidth(tNumber, vEntryBox.getWidth()); + meta.setHeight(tNumber, vEntryBox.getHeight()); + } MP4::Box initBox = vEntryBox.getCLAP(); if (initBox.isType("avcC")){ - myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize()); + meta.setInit(tNumber, initBox.payload(), initBox.payloadSize()); } initBox = vEntryBox.getPASP(); if (initBox.isType("avcC")){ - myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize()); + meta.setInit(tNumber, initBox.payload(), initBox.payloadSize()); } - /// this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but we do need correct data... - if (!myMeta.tracks[trackNo].width){ + /// this is a hacky way around invalid FLV data (since it gets ignored nearly + /// everywhere, but we do need correct data... + if (!meta.getWidth(tNumber)){ h264::sequenceParameterSet sps; - sps.fromDTSCInit(myMeta.tracks[trackNo].init); + sps.fromDTSCInit(meta.getInit(tNumber)); h264::SPSMeta spsChar = sps.getCharacteristics(); - myMeta.tracks[trackNo].width = spsChar.width; - myMeta.tracks[trackNo].height = spsChar.height; + meta.setWidth(tNumber, spsChar.width); + meta.setHeight(tNumber, spsChar.height); } } if (sType == "hev1" || sType == "hvc1"){ MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox; - myMeta.tracks[trackNo].type = "video"; - myMeta.tracks[trackNo].codec = "HEVC"; - if (!myMeta.tracks[trackNo].width){ - myMeta.tracks[trackNo].width = vEntryBox.getWidth(); - myMeta.tracks[trackNo].height = vEntryBox.getHeight(); + meta.setType(tNumber, "video"); + meta.setCodec(tNumber, "HEVC"); + if (!meta.getWidth(tNumber)){ + meta.setWidth(tNumber, vEntryBox.getWidth()); + meta.setHeight(tNumber, vEntryBox.getHeight()); } MP4::Box initBox = vEntryBox.getCLAP(); if (initBox.isType("hvcC")){ - myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize()); + meta.setInit(tNumber, initBox.payload(), initBox.payloadSize()); } initBox = vEntryBox.getPASP(); if (initBox.isType("hvcC")){ - myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize()); + meta.setInit(tNumber, initBox.payload(), initBox.payloadSize()); } } if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){ MP4::AudioSampleEntry &aEntryBox = (MP4::AudioSampleEntry &)sEntryBox; - myMeta.tracks[trackNo].type = "audio"; - myMeta.tracks[trackNo].channels = aEntryBox.getChannelCount(); - myMeta.tracks[trackNo].rate = aEntryBox.getSampleRate(); + meta.setType(tNumber, "audio"); + meta.setChannels(tNumber, aEntryBox.getChannelCount()); + meta.setRate(tNumber, aEntryBox.getSampleRate()); if (sType == "ac-3"){ - myMeta.tracks[trackNo].codec = "AC3"; + meta.setCodec(tNumber, "AC3"); }else{ MP4::ESDS esdsBox = (MP4::ESDS &)(aEntryBox.getCodecBox()); - myMeta.tracks[trackNo].codec = esdsBox.getCodec(); - myMeta.tracks[trackNo].init = esdsBox.getInitData(); + meta.setCodec(tNumber, esdsBox.getCodec()); + meta.setInit(tNumber, esdsBox.getInitData()); } - myMeta.tracks[trackNo].size = 16; ///\todo this might be nice to calculate from mp4 file; + meta.setSize(tNumber, 16); ///\todo this might be nice to calculate from mp4 file; } - if (sType == "tx3g"){// plain text subtitles - myMeta.tracks[trackNo].type = "meta"; - myMeta.tracks[trackNo].codec = "subtitle"; + meta.setType(tNumber, "meta"); + meta.setCodec(tNumber, "subtitle"); } MP4::STSS stssBox = stblBox.getChild(); @@ -374,7 +385,7 @@ namespace Mist{ nextFirstChunk = (stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount); } - BsetPart.keyframe = (myMeta.tracks[trackNo].type == "video" && stssIndex < stssCount && + BsetPart.keyframe = (meta.getType(tNumber) == "video" && stssIndex < stssCount && stszIndex + 1 == stssBox.getSampleNumber(stssIndex)); if (BsetPart.keyframe){++stssIndex;} // in bpos set @@ -417,12 +428,12 @@ namespace Mist{ long long packSendSize = 0; packSendSize = 24 + (BsetPart.timeOffset ? 17 : 0) + (BsetPart.bpos ? 15 : 0) + 19 + stszBox.getEntrySize(stszIndex) + 11 - 2 + 19; - myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, - stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize); + meta.update(BsetPart.time, BsetPart.timeOffset, tNumber, + stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize); } }else{ - myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, - stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe); + meta.update(BsetPart.time, BsetPart.timeOffset, tNumber, + stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe); } } } @@ -436,11 +447,11 @@ namespace Mist{ clearerr(inFile); // outputting dtsh file - myMeta.toFile(config->getString("input") + ".dtsh"); + M.toFile(config->getString("input") + ".dtsh"); return true; } - void inputMP4::getNext(bool smart){// get next part from track in stream + void inputMP4::getNext(size_t idx){// get next part from track in stream if (curPositions.empty()){ thisPacket.null(); return; @@ -450,17 +461,17 @@ namespace Mist{ curPositions.erase(curPositions.begin()); bool isKeyframe = false; - if (nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){ + DTSC::Keys keys(M.keys(curPart.trackID)); + uint32_t nextKeyNum = nextKeyframe[curPart.trackID]; + if (nextKeyNum < keys.getEndValid()){ // checking if this is a keyframe - if (myMeta.tracks[curPart.trackID].type == "video" && - (long long int)curPart.time == - myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime()){ + if (meta.getType(curPart.trackID) == "video" && curPart.time == keys.getTime(nextKeyNum)){ isKeyframe = true; } // if a keyframe has passed, we find the next keyframe - if (myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime() <= - (long long int)curPart.time){ - nextKeyframe[curPart.trackID]++; + if (keys.getTime(nextKeyNum) <= curPart.time){ + ++nextKeyframe[curPart.trackID]; + ++nextKeyNum; } } if (fseeko(inFile, curPart.bpos, SEEK_SET)){ @@ -478,85 +489,63 @@ namespace Mist{ return; } - if (myMeta.tracks[curPart.trackID].codec == "subtitle"){ + if (M.getCodec(curPart.trackID) == "subtitle"){ unsigned int txtLen = Bit::btohs(data); if (!txtLen && false){ curPart.index++; - return getNext(smart); - // thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, " ", 1, 0/*Note: no bpos*/, isKeyframe); - }else{ - - static JSON::Value thisPack; - thisPack.null(); - thisPack["trackid"] = (uint64_t)curPart.trackID; - thisPack["bpos"] = curPart.bpos; //(long long)fileSource.tellg(); - thisPack["data"] = std::string(data + 2, txtLen); - thisPack["time"] = curPart.time; - if (curPart.duration){thisPack["duration"] = curPart.duration;} - thisPack["keyframe"] = true; - // Write the json value to lastpack - std::string tmpStr = thisPack.toNetPacked(); - thisPacket.reInit(tmpStr.data(), tmpStr.size()); - // return; - - // thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data+2, txtLen, 0/*Note: no bpos*/, isKeyframe); + return getNext(idx); } + static JSON::Value thisPack; + thisPack.null(); + thisPack["trackid"] = curPart.trackID; + thisPack["bpos"] = curPart.bpos; //(long long)fileSource.tellg(); + thisPack["data"] = std::string(data + 2, txtLen); + thisPack["time"] = curPart.time; + if (curPart.duration){thisPack["duration"] = curPart.duration;} + thisPack["keyframe"] = true; + std::string tmpStr = thisPack.toNetPacked(); + thisPacket.reInit(tmpStr.data(), tmpStr.size()); }else{ - thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, - 0 /*Note: no bpos*/, isKeyframe); + thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, 0, isKeyframe); } // get the next part for this track curPart.index++; - if (curPart.index < headerData[curPart.trackID].size()){ - headerData[curPart.trackID].getPart(curPart.index, curPart.bpos, curPart.size, curPart.time, - curPart.offset, curPart.duration); + if (curPart.index < headerData(M.getID(curPart.trackID)).size()){ + headerData(M.getID(curPart.trackID)) + .getPart(curPart.index, curPart.bpos, curPart.size, curPart.time, curPart.offset, curPart.duration); curPositions.insert(curPart); } } - void inputMP4::seek(int seekTime){// seek to a point + void inputMP4::seek(uint64_t seekTime, size_t idx){// seek to a point nextKeyframe.clear(); - // for all tracks curPositions.clear(); - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - nextKeyframe[*it] = 0; - mp4PartTime addPart; - addPart.bpos = 0; - addPart.size = 0; - addPart.time = 0; - addPart.trackID = *it; - // for all indexes in those tracks - for (unsigned int i = 0; i < headerData[*it].size(); i++){ - // if time > seekTime - headerData[*it].getPart(i, addPart.bpos, addPart.size, addPart.time, addPart.offset, addPart.duration); - // check for keyframe time in myMeta and update nextKeyframe - // - if (myMeta.tracks[*it].keys[(nextKeyframe[*it])].getTime() < addPart.time){ - nextKeyframe[*it]++; - } - if (addPart.time >= seekTime){ - addPart.index = i; - // use addPart thingy in time set and break - curPositions.insert(addPart); - break; - }// end if time > seektime - }// end for all indexes - }// rof all tracks + if (idx != INVALID_TRACK_ID){ + handleSeek(seekTime, idx); + }else{ + std::set tracks = M.getValidTracks(); + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++){ + handleSeek(seekTime, *it); + } + } } - void inputMP4::trackSelect(std::string trackSpec){ - selectedTracks.clear(); - long long int index; - while (trackSpec != ""){ - index = trackSpec.find(' '); - selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str())); - VERYHIGH_MSG("Added track %d, index = %lld, (index == npos) = %d", - atoi(trackSpec.substr(0, index).c_str()), index, index == std::string::npos); - if (index != std::string::npos){ - trackSpec.erase(0, index + 1); - }else{ - trackSpec = ""; + void inputMP4::handleSeek(uint64_t seekTime, size_t idx){ + nextKeyframe[idx] = 0; + mp4PartTime addPart; + addPart.trackID = idx; + // for all stsz samples in those tracks + mp4TrackHeader &thisHeader = headerData(M.getID(idx)); + size_t headerDataSize = thisHeader.size(); + DTSC::Keys keys(M.keys(idx)); + for (size_t i = 0; i < headerDataSize; i++){ + thisHeader.getPart(i, addPart.bpos, addPart.size, addPart.time, addPart.offset, addPart.duration); + if (keys.getTime(nextKeyframe[idx]) < addPart.time){nextKeyframe[idx]++;} + if (addPart.time >= seekTime){ + addPart.index = i; + curPositions.insert(addPart); + break; } } } diff --git a/src/input/input_mp4.h b/src/input/input_mp4.h index 145a46ff..b5987d3b 100644 --- a/src/input/input_mp4.h +++ b/src/input/input_mp4.h @@ -5,7 +5,7 @@ namespace Mist{ class mp4PartTime{ public: - mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0), duration(0){} + mp4PartTime() : time(0), duration(0), offset(0), trackID(0), bpos(0), size(0), index(0){} bool operator<(const mp4PartTime &rhs) const{ if (time < rhs.time){return true;} if (time > rhs.time){return false;} @@ -40,6 +40,7 @@ namespace Mist{ class mp4TrackHeader{ public: mp4TrackHeader(); + size_t trackId; void read(MP4::TRAK &trakBox); MP4::STCO stcoBox; MP4::CO64 co64Box; @@ -80,21 +81,24 @@ namespace Mist{ bool preRun(); bool readHeader(); bool needHeader(){return true;} - void getNext(bool smart = true); - void seek(int seekTime); - void trackSelect(std::string trackSpec); + void getNext(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); + void handleSeek(uint64_t seekTime, size_t idx); FILE *inFile; - std::map headerData; + mp4TrackHeader &headerData(size_t trackID); + + std::deque trackHeaders; std::set curPositions; // remember last seeked keyframe; - std::map nextKeyframe; + std::map nextKeyframe; // these next two variables keep a buffer for reading from filepointer inFile; uint64_t malSize; - char *data; ///\todo rename this variable to a more sensible name, it is a temporary piece of memory to read from files + char *data; ///\todo rename this variable to a more sensible name, it is a temporary piece of + /// memory to read from files }; }// namespace Mist diff --git a/src/input/input_ogg.cpp b/src/input/input_ogg.cpp index c59b1b9c..e8a04aa8 100644 --- a/src/input/input_ogg.cpp +++ b/src/input/input_ogg.cpp @@ -21,9 +21,8 @@ namespace Mist{ retval["time"] = time; retval["trackid"] = tid; std::string tmpString = ""; - for (unsigned int i = 0; i < parts.size(); i++){tmpString += parts[i];} + for (size_t i = 0; i < parts.size(); i++){tmpString += parts[i];} retval["data"] = tmpString; - // INFO_MSG("Setting bpos for packet on track %llu, time %llu, to %llu", tid, time, bytepos); retval["bpos"] = bytepos; if (myCodec == OGG::THEORA){ if (!theora::isHeader(tmpString.data(), tmpString.size())){ @@ -34,12 +33,6 @@ namespace Mist{ return retval; } - /* - unsigned long oggTrack::getBlockSize(unsigned int vModeIndex){//WTF!!? - return blockSize[vModes[vModeIndex].blockFlag]; - - } - */ inputOGG::inputOGG(Util::Config *cfg) : Input(cfg){ capa["name"] = "OGG"; capa["desc"] = "This input allows streaming of OGG files as Video on Demand."; @@ -68,70 +61,74 @@ namespace Mist{ ///\todo check if all trackID (tid) instances are replaced with bitstream serial numbers void inputOGG::parseBeginOfStream(OGG::Page &bosPage){ // long long int tid = snum2tid.size() + 1; - unsigned int tid = bosPage.getBitstreamSerialNumber(); + size_t tid = bosPage.getBitstreamSerialNumber(); + size_t idx = M.trackIDToIndex(tid, getpid()); + if (idx == INVALID_TRACK_ID){idx = meta.addTrack();} if (memcmp(bosPage.getSegment(0) + 1, "theora", 6) == 0){ theora::header tmpHead((char *)bosPage.getSegment(0), bosPage.getSegmentLen(0)); - oggTracks[tid].codec = OGG::THEORA; - oggTracks[tid].msPerFrame = (double)(tmpHead.getFRD() * 1000) / (double)tmpHead.getFRN(); // this should be: 1000/( tmpHead.getFRN()/ tmpHead.getFRD() ) - oggTracks[tid].KFGShift = tmpHead.getKFGShift(); // store KFGShift for granule calculations - myMeta.tracks[tid].type = "video"; - myMeta.tracks[tid].codec = "theora"; - myMeta.tracks[tid].trackID = tid; - myMeta.tracks[tid].fpks = (tmpHead.getFRN() * 1000) / tmpHead.getFRD(); - myMeta.tracks[tid].height = tmpHead.getPICH(); - myMeta.tracks[tid].width = tmpHead.getPICW(); - if (!myMeta.tracks[tid].init.size()){ - myMeta.tracks[tid].init = (char)((bosPage.getPayloadSize() >> 8) & 0xFF); - myMeta.tracks[tid].init += (char)(bosPage.getPayloadSize() & 0xFF); - myMeta.tracks[tid].init.append(bosPage.getSegment(0), bosPage.getSegmentLen(0)); + oggTracks[idx].codec = OGG::THEORA; + oggTracks[idx].msPerFrame = (double)(tmpHead.getFRD() * 1000) / (double)tmpHead.getFRN(); // this should be: 1000/( tmpHead.getFRN()/ tmpHead.getFRD() ) + oggTracks[idx].KFGShift = tmpHead.getKFGShift(); // store KFGShift for granule calculations + meta.setType(idx, "video"); + meta.setCodec(idx, "theora"); + meta.setID(idx, tid); + meta.setFpks(idx, (double)(tmpHead.getFRN() * 1000) / tmpHead.getFRD()); + meta.setHeight(idx, tmpHead.getPICH()); + meta.setWidth(idx, tmpHead.getPICW()); + if (!M.getInit(idx).size()){ + std::string init = " "; + Bit::htobs((char *)init.data(), bosPage.getPayloadSize()); + init.append(bosPage.getSegment(0), bosPage.getSegmentLen(0)); + meta.setInit(idx, init); } - INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str()); + INFO_MSG("Track with id %zu is %s", tid, M.getCodec(tid).c_str()); } if (memcmp(bosPage.getSegment(0) + 1, "vorbis", 6) == 0){ vorbis::header tmpHead((char *)bosPage.getSegment(0), bosPage.getSegmentLen(0)); - oggTracks[tid].codec = OGG::VORBIS; - oggTracks[tid].msPerFrame = (double)1000.0f / tmpHead.getAudioSampleRate(); - DEBUG_MSG(DLVL_DEVEL, "vorbis trackID: %d msperFrame %f ", tid, oggTracks[tid].msPerFrame); - oggTracks[tid].channels = tmpHead.getAudioChannels(); - oggTracks[tid].blockSize[0] = 1 << tmpHead.getBlockSize0(); - oggTracks[tid].blockSize[1] = 1 << tmpHead.getBlockSize1(); + oggTracks[idx].codec = OGG::VORBIS; + oggTracks[idx].msPerFrame = (double)1000.0f / tmpHead.getAudioSampleRate(); + oggTracks[idx].channels = tmpHead.getAudioChannels(); + oggTracks[idx].blockSize[0] = 1 << tmpHead.getBlockSize0(); + oggTracks[idx].blockSize[1] = 1 << tmpHead.getBlockSize1(); + DEVEL_MSG("vorbis trackID: %zu msperFrame %f ", tid, oggTracks[idx].msPerFrame); // Abusing .contBuffer for temporarily storing the idHeader - bosPage.getSegment(0, oggTracks[tid].contBuffer); + bosPage.getSegment(0, oggTracks[idx].contBuffer); - myMeta.tracks[tid].type = "audio"; - myMeta.tracks[tid].codec = "vorbis"; - myMeta.tracks[tid].rate = tmpHead.getAudioSampleRate(); - myMeta.tracks[tid].trackID = tid; - myMeta.tracks[tid].channels = tmpHead.getAudioChannels(); - INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str()); + meta.setType(idx, "audio"); + meta.setCodec(idx, "vorbis"); + meta.setRate(idx, tmpHead.getAudioSampleRate()); + meta.setID(idx, tid); + meta.setChannels(idx, tmpHead.getAudioChannels()); + INFO_MSG("Track with id %zu is %s", tid, M.getCodec(idx).c_str()); } if (memcmp(bosPage.getSegment(0), "OpusHead", 8) == 0){ - oggTracks[tid].codec = OGG::OPUS; - myMeta.tracks[tid].type = "audio"; - myMeta.tracks[tid].codec = "opus"; - myMeta.tracks[tid].rate = 48000; - myMeta.tracks[tid].trackID = tid; - myMeta.tracks[tid].init.assign(bosPage.getSegment(0), bosPage.getSegmentLen(0)); - myMeta.tracks[tid].channels = myMeta.tracks[tid].init[9]; - INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str()); + oggTracks[idx].codec = OGG::OPUS; + meta.setType(idx, "audio"); + meta.setCodec(idx, "opus"); + meta.setRate(idx, 48000); + meta.setID(idx, tid); + meta.setInit(idx, bosPage.getSegment(0), bosPage.getSegmentLen(0)); + meta.setChannels(idx, M.getInit(idx)[9]); + INFO_MSG("Track with id %zu is %s", tid, M.getCodec(idx).c_str()); } } bool inputOGG::readHeader(){ + meta.reInit(config->getString("streamname"), true); OGG::Page myPage; fseek(inFile, 0, SEEK_SET); while (myPage.read(inFile)){// assumes all headers are sent before any data - unsigned int tid = myPage.getBitstreamSerialNumber(); + size_t tid = myPage.getBitstreamSerialNumber(); + size_t idx = M.trackIDToIndex(tid, getpid()); if (myPage.getHeaderType() & OGG::BeginOfStream){ parseBeginOfStream(myPage); - INFO_MSG("Read BeginOfStream for track %d", tid); + INFO_MSG("Read BeginOfStream for track %zu", tid); continue; // Continue reading next pages } bool readAllHeaders = true; - for (std::map::iterator it = oggTracks.begin(); - it != oggTracks.end(); it++){ + for (std::map::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){ if (!it->second.parsedHeaders){ readAllHeaders = false; break; @@ -139,143 +136,142 @@ namespace Mist{ } if (readAllHeaders){break;} - // INFO_MSG("tid: %d",tid); - // Parsing headers - if (myMeta.tracks[tid].codec == "theora"){ - for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){ - unsigned long len = myPage.getSegmentLen(i); + if (M.getCodec(idx) == "theora"){ + for (size_t i = 0; i < myPage.getAllSegments().size(); i++){ + size_t len = myPage.getSegmentLen(i); theora::header tmpHead((char *)myPage.getSegment(i), len); if (!tmpHead.isHeader()){// not copying the header anymore, should this check isHeader? - DEBUG_MSG(DLVL_FAIL, "Theora Header read failed!"); + FAIL_MSG("Theora Header read failed!"); return false; } switch (tmpHead.getHeaderType()){ // Case 0 is being handled by parseBeginOfStream case 1:{ - myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF); - myMeta.tracks[tid].init += (char)(len & 0xFF); - myMeta.tracks[tid].init.append(myPage.getSegment(i), len); + std::string init = M.getInit(idx); + init += (char)((len >> 8) & 0xFF); + init += (char)(len & 0xFF); + init.append(myPage.getSegment(i), len); + meta.setInit(idx, init); break; } case 2:{ - myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF); - myMeta.tracks[tid].init += (char)(len & 0xFF); - myMeta.tracks[tid].init.append(myPage.getSegment(i), len); - oggTracks[tid].lastGran = 0; - oggTracks[tid].parsedHeaders = true; + std::string init = M.getInit(idx); + init += (char)((len >> 8) & 0xFF); + init += (char)(len & 0xFF); + init.append(myPage.getSegment(i), len); + meta.setInit(idx, init); + oggTracks[idx].lastGran = 0; + oggTracks[idx].parsedHeaders = true; break; } } } } - if (myMeta.tracks[tid].codec == "vorbis"){ - for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){ - unsigned long len = myPage.getSegmentLen(i); + if (M.getCodec(idx) == "vorbis"){ + for (size_t i = 0; i < myPage.getAllSegments().size(); i++){ + size_t len = myPage.getSegmentLen(i); vorbis::header tmpHead((char *)myPage.getSegment(i), len); if (!tmpHead.isHeader()){ - DEBUG_MSG(DLVL_FAIL, "Header read failed!"); + FAIL_MSG("Header read failed!"); return false; } switch (tmpHead.getHeaderType()){ // Case 1 is being handled by parseBeginOfStream case 3:{ // we have the first header stored in contBuffer - myMeta.tracks[tid].init += (char)0x02; + std::string init = M.getInit(idx); + init += (char)0x02; // ID header size - for (unsigned int j = 0; j < (oggTracks[tid].contBuffer.size() / 255); j++){ - myMeta.tracks[tid].init += (char)0xFF; + for (size_t j = 0; j < (oggTracks[idx].contBuffer.size() / 255); j++){ + init += (char)0xFF; } - myMeta.tracks[tid].init += (char)(oggTracks[tid].contBuffer.size() % 255); + init += (char)(oggTracks[idx].contBuffer.size() % 255); // Comment header size - for (unsigned int j = 0; j < (len / 255); j++){ - myMeta.tracks[tid].init += (char)0xFF; - } - myMeta.tracks[tid].init += (char)(len % 255); - myMeta.tracks[tid].init += oggTracks[tid].contBuffer; - oggTracks[tid].contBuffer.clear(); - myMeta.tracks[tid].init.append(myPage.getSegment(i), len); + for (size_t j = 0; j < (len / 255); j++){init += (char)0xFF;} + init += (char)(len % 255); + init += oggTracks[idx].contBuffer; + oggTracks[idx].contBuffer.clear(); + init.append(myPage.getSegment(i), len); + meta.setInit(idx, init); break; } case 5:{ - myMeta.tracks[tid].init.append(myPage.getSegment(i), len); - oggTracks[tid].vModes = tmpHead.readModeDeque(oggTracks[tid].channels); - oggTracks[tid].parsedHeaders = true; + std::string init = M.getInit(idx); + init.append(myPage.getSegment(i), len); + meta.setInit(idx, init); + oggTracks[idx].vModes = tmpHead.readModeDeque(oggTracks[idx].channels); + oggTracks[idx].parsedHeaders = true; break; } } } } - if (myMeta.tracks[tid].codec == "opus"){oggTracks[tid].parsedHeaders = true;} + if (M.getCodec(idx) == "opus"){oggTracks[idx].parsedHeaders = true;} } - for (std::map::iterator it = oggTracks.begin(); - it != oggTracks.end(); it++){ + for (std::map::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){ fseek(inFile, 0, SEEK_SET); - INFO_MSG("Finding first data for track %lu", it->first); + INFO_MSG("Finding first data for track %zu", it->first); position tmp = seekFirstData(it->first); - if (tmp.trackID){ - currentPositions.insert(tmp); - }else{ - INFO_MSG("missing track: %lu", it->first); - } + currentPositions.insert(tmp); } getNext(); while (thisPacket){ - myMeta.update(thisPacket); + meta.update(thisPacket); getNext(); } - myMeta.toFile(config->getString("input") + ".dtsh"); + meta.toFile(config->getString("input") + ".dtsh"); return true; } - position inputOGG::seekFirstData(long long unsigned int tid){ + position inputOGG::seekFirstData(size_t idx){ fseek(inFile, 0, SEEK_SET); position res; res.time = 0; - res.trackID = tid; + res.trackID = idx; res.segmentNo = 0; bool readSuccesfull = true; bool quitloop = false; while (!quitloop){ quitloop = true; res.bytepos = ftell(inFile); - readSuccesfull = oggTracks[tid].myPage.read(inFile); + readSuccesfull = oggTracks[idx].myPage.read(inFile); if (!readSuccesfull){ quitloop = true; // break :( break; } - if (oggTracks[tid].myPage.getBitstreamSerialNumber() != tid){ + if (oggTracks[idx].myPage.getBitstreamSerialNumber() != M.getID(idx)){ quitloop = false; continue; } - if (oggTracks[tid].myPage.getHeaderType() != OGG::Plain){ + if (oggTracks[idx].myPage.getHeaderType() != OGG::Plain){ quitloop = false; continue; } - if (oggTracks[tid].codec == OGG::OPUS){ - if (std::string(oggTracks[tid].myPage.getSegment(0), 2) == "Op"){quitloop = false;} + if (oggTracks[idx].codec == OGG::OPUS){ + if (std::string(oggTracks[idx].myPage.getSegment(0), 2) == "Op"){quitloop = false;} } - if (oggTracks[tid].codec == OGG::VORBIS){ - vorbis::header tmpHead((char *)oggTracks[tid].myPage.getSegment(0), - oggTracks[tid].myPage.getSegmentLen(0)); + if (oggTracks[idx].codec == OGG::VORBIS){ + vorbis::header tmpHead((char *)oggTracks[idx].myPage.getSegment(0), + oggTracks[idx].myPage.getSegmentLen(0)); if (tmpHead.isHeader()){quitloop = false;} } - if (oggTracks[tid].codec == OGG::THEORA){ - theora::header tmpHead((char *)oggTracks[tid].myPage.getSegment(0), - oggTracks[tid].myPage.getSegmentLen(0)); + if (oggTracks[idx].codec == OGG::THEORA){ + theora::header tmpHead((char *)oggTracks[idx].myPage.getSegment(0), + oggTracks[idx].myPage.getSegmentLen(0)); if (tmpHead.isHeader()){quitloop = false;} } - }// while ( oggTracks[tid].myPage.getHeaderType() != OGG::Plain && readSuccesfull && oggTracks[tid].myPage.getBitstreamSerialNumber() != tid); - INFO_MSG("seek first bytepos: %llu tid: %llu oggTracks[tid].myPage.getHeaderType(): %d ", - res.bytepos, tid, oggTracks[tid].myPage.getHeaderType()); + } + INFO_MSG("seek first bytepos: %" PRIu64 " tid: %zu oggTracks[idx].myPage.getHeaderType(): %d ", + res.bytepos, idx, oggTracks[idx].myPage.getHeaderType()); if (!readSuccesfull){res.trackID = 0;} return res; } - void inputOGG::getNext(bool smart){ + void inputOGG::getNext(size_t idx){ if (!currentPositions.size()){ thisPacket.null(); return; @@ -287,7 +283,7 @@ namespace Mist{ thisSegment.tid = curPos.trackID; thisSegment.time = curPos.time; thisSegment.bytepos = curPos.bytepos + curPos.segmentNo; - unsigned int oldSegNo = curPos.segmentNo; + size_t oldSegNo = curPos.segmentNo; fseek(inFile, curPos.bytepos, SEEK_SET); OGG::Page curPage; curPage.read(inFile); @@ -296,7 +292,7 @@ namespace Mist{ bool readFullPacket = false; if (curPos.segmentNo == curPage.getAllSegments().size() - 1){ OGG::Page tmpPage; - unsigned int bPos; + uint64_t bPos; while (!readFullPacket){ bPos = ftell(inFile); //<-- :( if (!tmpPage.read(inFile)){break;} @@ -315,11 +311,11 @@ namespace Mist{ }else{ curPos.segmentNo++; - // if (oggTracks[thisSegment.tid].codec == OGG::THEORA && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull) - // && curPos.segmentNo == curPage.getAllSegments().size() - 1){//if the next segment is the last one on the page, the (theora) granule should be used to sync the time for the current segment - if ((oggTracks[thisSegment.tid].codec == OGG::THEORA || oggTracks[thisSegment.tid].codec == OGG::VORBIS) && + if ((oggTracks[curPos.trackID].codec == OGG::THEORA || oggTracks[curPos.trackID].codec == OGG::VORBIS) && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull) && - curPos.segmentNo == curPage.getAllSegments().size() - 1){// if the next segment is the last one on the page, the (theora) granule should be used to sync the time for the current segment + curPos.segmentNo == curPage.getAllSegments().size() - + 1){// if the next segment is the last one on the page, the (theora) granule + // should be used to sync the time for the current segment OGG::Page tmpPage; while (tmpPage.read(inFile) && tmpPage.getBitstreamSerialNumber() != thisSegment.tid){} if ((tmpPage.getBitstreamSerialNumber() == thisSegment.tid) && tmpPage.getHeaderType() == OGG::Continued){ @@ -328,37 +324,36 @@ namespace Mist{ } readFullPacket = true; } - std::string tmpStr = thisSegment.toJSON(oggTracks[thisSegment.tid].codec).toNetPacked(); + std::string tmpStr = thisSegment.toJSON(oggTracks[curPos.trackID].codec).toNetPacked(); thisPacket.reInit(tmpStr.data(), tmpStr.size()); - if (oggTracks[thisSegment.tid].codec == OGG::VORBIS){ - unsigned long blockSize = 0; + if (oggTracks[curPos.trackID].codec == OGG::VORBIS){ + size_t blockSize = 0; Utils::bitstreamLSBF packet; packet.append((char *)curPage.getSegment(oldSegNo), curPage.getSegmentLen(oldSegNo)); if (!packet.get(1)){ // Read index first - unsigned long vModeIndex = packet.get(vorbis::ilog(oggTracks[thisSegment.tid].vModes.size() - 1)); + size_t vModeIndex = packet.get(vorbis::ilog(oggTracks[curPos.trackID].vModes.size() - 1)); blockSize = - oggTracks[thisSegment.tid].blockSize[oggTracks[thisSegment.tid].vModes[vModeIndex].blockFlag]; // almost readable. + oggTracks[curPos.trackID].blockSize[oggTracks[curPos.trackID].vModes[vModeIndex].blockFlag]; // almost + // readable. }else{ - DEBUG_MSG(DLVL_WARN, "Packet type != 0"); + WARN_MSG("Packet type != 0"); } - curPos.time += oggTracks[thisSegment.tid].msPerFrame * (blockSize / oggTracks[thisSegment.tid].channels); - }else if (oggTracks[thisSegment.tid].codec == OGG::THEORA){ + curPos.time += oggTracks[curPos.trackID].msPerFrame * (blockSize / oggTracks[curPos.trackID].channels); + }else if (oggTracks[curPos.trackID].codec == OGG::THEORA){ if (lastCompleteSegment == true && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull)){// this segment should be used to sync time using granule - long long unsigned int parseGranuleUpper = - curPage.getGranulePosition() >> oggTracks[thisSegment.tid].KFGShift; - long long unsigned int parseGranuleLower(curPage.getGranulePosition() & - ((1 << oggTracks[thisSegment.tid].KFGShift) - 1)); - thisSegment.time = - oggTracks[thisSegment.tid].msPerFrame * (parseGranuleUpper + parseGranuleLower - 1); + uint64_t parseGranuleUpper = curPage.getGranulePosition() >> oggTracks[curPos.trackID].KFGShift; + uint64_t parseGranuleLower(curPage.getGranulePosition() & + ((1 << oggTracks[curPos.trackID].KFGShift) - 1)); + thisSegment.time = oggTracks[curPos.trackID].msPerFrame * (parseGranuleUpper + parseGranuleLower - 1); curPos.time = thisSegment.time; - std::string tmpStr = thisSegment.toJSON(oggTracks[thisSegment.tid].codec).toNetPacked(); + std::string tmpStr = thisSegment.toJSON(oggTracks[curPos.trackID].codec).toNetPacked(); thisPacket.reInit(tmpStr.data(), tmpStr.size()); // INFO_MSG("thisTime: %d", thisPacket.getTime()); } - curPos.time += oggTracks[thisSegment.tid].msPerFrame; - }else if (oggTracks[thisSegment.tid].codec == OGG::OPUS){ + curPos.time += oggTracks[curPos.trackID].msPerFrame; + }else if (oggTracks[curPos.trackID].codec == OGG::OPUS){ if (thisSegment.parts.size()){ curPos.time += Opus::Opus_getDuration(thisSegment.parts.front().data()); } @@ -366,21 +361,22 @@ namespace Mist{ if (readFullPacket){currentPositions.insert(curPos);} }// getnext() - long long unsigned int inputOGG::calcGranuleTime(unsigned long tid, long long unsigned int granule){ - switch (oggTracks[tid].codec){ + uint64_t inputOGG::calcGranuleTime(size_t tid, uint64_t granule){ + size_t idx = M.trackIDToIndex(tid, getpid()); + switch (oggTracks[idx].codec){ case OGG::VORBIS: - return granule * oggTracks[tid].msPerFrame; //= samples * samples per second + return granule * oggTracks[idx].msPerFrame; //= samples * samples per second break; case OGG::OPUS: return granule / 48; // always 48kHz break; case OGG::THEORA:{ - long long unsigned int parseGranuleUpper = granule >> oggTracks[tid].KFGShift; - long long unsigned int parseGranuleLower = (granule & ((1 << oggTracks[tid].KFGShift) - 1)); - return (parseGranuleUpper + parseGranuleLower) * oggTracks[tid].msPerFrame; //= frames * msPerFrame + uint64_t parseGranuleUpper = granule >> oggTracks[idx].KFGShift; + uint64_t parseGranuleLower = (granule & ((1 << oggTracks[idx].KFGShift) - 1)); + return (parseGranuleUpper + parseGranuleLower) * oggTracks[idx].msPerFrame; //= frames * msPerFrame break; } - default: DEBUG_MSG(DLVL_WARN, "Unknown codec, can not calculate time from granule"); break; + default: WARN_MSG("Unknown codec, can not calculate time from granule"); break; } return 0; } @@ -398,27 +394,28 @@ namespace Mist{ } #endif - void inputOGG::seek(int seekTime){ + void inputOGG::seek(uint64_t seekTime, size_t idx){ currentPositions.clear(); - DEBUG_MSG(DLVL_MEDIUM, "Seeking to %dms", seekTime); + MEDIUM_MSG("Seeking to %" PRIu64 "ms", seekTime); // for every track - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ // find first keyframe before keyframe with ms > seektime position tmpPos; - tmpPos.trackID = *it; - tmpPos.time = myMeta.tracks[*it].keys.begin()->getTime(); - tmpPos.bytepos = myMeta.tracks[*it].keys.begin()->getBpos(); - for (std::deque::iterator ot = myMeta.tracks[*it].keys.begin(); - ot != myMeta.tracks[*it].keys.end(); ot++){ - if (ot->getTime() > seekTime){ + tmpPos.trackID = it->first; + DTSC::Keys keys(M.keys(it->first)); + tmpPos.time = keys.getTime(keys.getFirstValid()); + tmpPos.bytepos = keys.getBpos(keys.getFirstValid()); + for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); ++i){ + if (keys.getTime(i) > seekTime){ break; }else{ - tmpPos.time = ot->getTime(); - tmpPos.bytepos = ot->getBpos(); + tmpPos.time = keys.getTime(i); + tmpPos.bytepos = keys.getBpos(i); } } - INFO_MSG("Found %dms for track %lu at %llu bytepos %llu", seekTime, *it, tmpPos.time, tmpPos.bytepos); + INFO_MSG("Found %" PRIu64 "ms for track %zu at %" PRIu64 " bytepos %" PRIu64, seekTime, + it->first, tmpPos.time, tmpPos.bytepos); int backChrs = std::min((uint64_t)280, tmpPos.bytepos - 1); fseek(inFile, tmpPos.bytepos - backChrs, SEEK_SET); char buffer[300]; @@ -428,28 +425,15 @@ namespace Mist{ loc = (char *)memrchr(buffer, 'O', (loc - buffer) - 1); // seek reverse } if (!loc){ - INFO_MSG("Unable to find a page boundary starting @ %llu, track %lu", tmpPos.bytepos, *it); + INFO_MSG("Unable to find a page boundary starting @ %" PRIu64 ", track %zu", tmpPos.bytepos, it->first); continue; } tmpPos.segmentNo = backChrs - (loc - buffer); tmpPos.bytepos -= tmpPos.segmentNo; - INFO_MSG("Track %lu, segment %llu found at bytepos %llu", *it, tmpPos.segmentNo, tmpPos.bytepos); + INFO_MSG("Track %zu, segment %zu found at bytepos %" PRIu64, it->first, tmpPos.segmentNo, + tmpPos.bytepos); currentPositions.insert(tmpPos); } } - - void inputOGG::trackSelect(std::string trackSpec){ - selectedTracks.clear(); - size_t index; - while (trackSpec != ""){ - index = trackSpec.find(' '); - selectedTracks.insert(atoll(trackSpec.substr(0, index).c_str())); - if (index != std::string::npos){ - trackSpec.erase(0, index + 1); - }else{ - trackSpec = ""; - } - } - } }// namespace Mist diff --git a/src/input/input_ogg.h b/src/input/input_ogg.h index 5cb5f521..a01f8d30 100644 --- a/src/input/input_ogg.h +++ b/src/input/input_ogg.h @@ -6,7 +6,7 @@ namespace Mist{ struct segPart{ char *segData; - unsigned int len; + size_t len; }; class segment{ @@ -25,43 +25,15 @@ namespace Mist{ struct position{ bool operator<(const position &rhs) const{ - if (time < rhs.time){ - return true; - }else{ - if (time == rhs.time){ - if (trackID < rhs.trackID){return true;} - } - } - return false; + if (time < rhs.time){return true;} + if (time > rhs.time){return false;} + return trackID < rhs.trackID; } uint64_t trackID; uint64_t time; uint64_t bytepos; uint64_t segmentNo; }; - /* - class oggTrack{ - public: - oggTrack() : lastTime(0), parsedHeaders(false), lastPageOffset(0), nxtSegment(0){} - codecType codec; - std::string contBuffer;//buffer for continuing pages - segment myBuffer; - double lastTime; - long long unsigned int lastGran; - bool parsedHeaders; - double msPerFrame; - long long unsigned int lastPageOffset; - OGG::Page myPage; - unsigned int nxtSegment; - //Codec specific elements - //theora - theora::header idHeader; - //vorbis - std::deque vModes; - char channels; - unsigned long blockSize[2]; - unsigned long getBlockSize(unsigned int vModeIndex); - };*/ class inputOGG : public Input{ public: @@ -72,18 +44,17 @@ namespace Mist{ bool checkArguments(); bool preRun(); bool readHeader(); - position seekFirstData(long long unsigned int tid); - void getNext(bool smart = true); - void seek(int seekTime); - void trackSelect(std::string trackSpec); + position seekFirstData(size_t tid); + void getNext(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); void parseBeginOfStream(OGG::Page &bosPage); std::set currentPositions; FILE *inFile; - std::map oggTracks; // this remembers all metadata for every track - std::set sortedSegments; // probably not needing this - long long unsigned int calcGranuleTime(unsigned long tid, long long unsigned int granule); - long long unsigned int calcSegmentDuration(unsigned long tid, std::string &segment); + std::map oggTracks; // this remembers all metadata for every track + std::set sortedSegments; // probably not needing this + uint64_t calcGranuleTime(size_t tid, uint64_t granule); + uint64_t calcSegmentDuration(size_t tid, std::string &segment); }; }// namespace Mist diff --git a/src/input/input_rtsp.cpp b/src/input/input_rtsp.cpp index 982a5c4c..b555eb12 100644 --- a/src/input/input_rtsp.cpp +++ b/src/input/input_rtsp.cpp @@ -15,7 +15,7 @@ void insertRTP(const uint64_t track, const RTP::Packet &p){ ///\param data The RTP Packet that needs to be sent ///\param len The size of data ///\param channel Not used here, but is kept for compatibility with sendTCP -void sendUDP(void *socket, char *data, unsigned int len, unsigned int channel){ +void sendUDP(void *socket, const char *data, size_t len, uint8_t channel){ ((Socket::UDPConnection *)socket)->SendNow(data, len); if (mainConn){mainConn->addUp(len);} } @@ -27,8 +27,10 @@ namespace Mist{ InputRTSP::InputRTSP(Util::Config *cfg) : Input(cfg){ needAuth = false; + setPacketOffset = false; + packetOffset = 0; TCPmode = true; - sdpState.myMeta = &myMeta; + sdpState.myMeta = &meta; sdpState.incomingPacketCallback = incomingPacket; classPointer = this; standAlone = false; @@ -153,28 +155,29 @@ namespace Mist{ } if (sdpState.tracks.size()){ bool atLeastOne = false; - for (std::map::iterator it = sdpState.tracks.begin(); + for (std::map::iterator it = sdpState.tracks.begin(); it != sdpState.tracks.end(); ++it){ transportSet = false; extraHeaders.clear(); extraHeaders["Transport"] = it->second.generateTransport(it->first, url.host, TCPmode); - sendCommand("SETUP", HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl(), "", &extraHeaders); + lastRequestedSetup = HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl(); + sendCommand("SETUP", lastRequestedSetup, "", &extraHeaders); if (tcpCon && transportSet){ atLeastOne = true; continue; } if (!atLeastOne && tcpCon){ INFO_MSG("Failed to set up transport for track %s, switching transports...", - myMeta.tracks[it->first].getIdentifier().c_str()); + M.getTrackIdentifier(it->first).c_str()); TCPmode = !TCPmode; extraHeaders["Transport"] = it->second.generateTransport(it->first, url.host, TCPmode); - sendCommand("SETUP", HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl(), "", &extraHeaders); + sendCommand("SETUP", lastRequestedSetup, "", &extraHeaders); } if (tcpCon && transportSet){ atLeastOne = true; continue; } - FAIL_MSG("Could not setup track %s!", myMeta.tracks[it->first].getIdentifier().c_str()); + FAIL_MSG("Could not setup track %s!", M.getTrackIdentifier(it->first).c_str()); tcpCon.close(); return; } @@ -183,11 +186,7 @@ namespace Mist{ extraHeaders.clear(); extraHeaders["Range"] = "npt=0.000-"; sendCommand("PLAY", url.getUrl(), "", &extraHeaders); - if (!TCPmode){ - connectedAt = Util::epoch() + 2208988800ll; - }else{ - tcpCon.setBlocking(true); - } + if (TCPmode){tcpCon.setBlocking(true);} } void InputRTSP::closeStreamSource(){ @@ -199,13 +198,9 @@ namespace Mist{ IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true); uint64_t startTime = Util::epoch(); uint64_t lastPing = Util::bootSecs(); - uint64_t lastSecs = 0; - while (config->is_active && nProxy.userClient.isAlive() && parsePacket()){ + while (keepAlive() && parsePacket()){ handleUDP(); - // keep going - nProxy.userClient.keepAlive(); - uint64_t currSecs = Util::bootSecs(); - if (currSecs - lastPing > 30){ + if (Util::bootSecs() - lastPing > 30){ sendCommand("GET_PARAMETER", url.getUrl(), ""); lastPing = Util::bootSecs(); } @@ -236,7 +231,7 @@ namespace Mist{ statsPage.finish(); if (!tcpCon){return "TCP connection closed";} if (!config->is_active){return "received deactivate signal";} - if (!nProxy.userClient.isAlive()){return "buffer shutdown";} + if (!keepAlive()){return "buffer shutdown";} return "Unknown"; } @@ -246,8 +241,7 @@ namespace Mist{ do{ // No new data? Sleep and retry, if connection still open if (!tcpCon.Received().size() || !tcpCon.Received().available(1)){ - if (!tcpCon.spool() && tcpCon && config->is_active && nProxy.userClient.isAlive()){ - nProxy.userClient.keepAlive(); + if (!tcpCon.spool() && tcpCon && keepAlive()){ Util::sleep(waitTime); if (!mustHave){return tcpCon;} } @@ -288,14 +282,15 @@ namespace Mist{ seenSDP = true; sdpState.parseSDP(recH.body); recH.Clean(); - INFO_MSG("SDP contained %llu tracks", myMeta.tracks.size()); + INFO_MSG("SDP contained %zu tracks", M.getValidTracks().size()); return true; } if (recH.hasHeader("Transport")){ INFO_MSG("Received setup response"); - uint32_t trackNo = sdpState.parseSetup(recH, url.host, ""); - if (trackNo){ - INFO_MSG("Parsed transport for track: %lu", trackNo); + recH.url = lastRequestedSetup; + size_t trackNo = sdpState.parseSetup(recH, url.host, ""); + if (trackNo != INVALID_TRACK_ID){ + INFO_MSG("Parsed transport for track: %zu", trackNo); transportSet = true; }else{ INFO_MSG("Could not parse transport string!"); @@ -319,17 +314,11 @@ namespace Mist{ recH.Clean(); return true; } - if (!tcpCon.spool() && tcpCon && config->is_active && nProxy.userClient.isAlive()){ - nProxy.userClient.keepAlive(); - Util::sleep(waitTime); - } + if (!tcpCon.spool() && tcpCon && keepAlive()){Util::sleep(waitTime);} continue; } if (!tcpCon.Received().available(4)){ - if (!tcpCon.spool() && tcpCon && config->is_active && nProxy.userClient.isAlive()){ - nProxy.userClient.keepAlive(); - Util::sleep(waitTime); - } + if (!tcpCon.spool() && tcpCon && keepAlive()){Util::sleep(waitTime);} continue; }// a TCP RTP packet, but not complete yet @@ -345,19 +334,26 @@ namespace Mist{ std::string tcpPacket = tcpCon.Received().remove(len + 4); RTP::Packet pkt(tcpPacket.data() + 4, len); uint8_t chan = tcpHead.data()[1]; - uint32_t trackNo = sdpState.getTrackNoForChannel(chan); - EXTREME_MSG("Received %ub RTP packet #%u on channel %u, time %llu", len, - (unsigned int)pkt.getSequence(), chan, pkt.getTimeStamp()); - if (!trackNo && (chan % 2) != 1){ + size_t trackNo = sdpState.getTrackNoForChannel(chan); + EXTREME_MSG("Received %ub RTP packet #%u on channel %u, time %" PRIu32, len, + pkt.getSequence(), chan, pkt.getTimeStamp()); + if ((trackNo == INVALID_TRACK_ID) && (chan % 2) != 1){ WARN_MSG("Received packet for unknown track number on channel %u", chan); } - if (trackNo){sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();} + if (trackNo != INVALID_TRACK_ID){ + sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence(); + } - sdpState.handleIncomingRTP(trackNo, pkt); + if (trackNo != INVALID_TRACK_ID){ + if (!userSelect.count(trackNo)){ + userSelect[trackNo].reload(streamName, trackNo, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK); + } + sdpState.handleIncomingRTP(trackNo, pkt); + } return true; - }while (tcpCon && config->is_active && nProxy.userClient.isAlive()); + }while (tcpCon && keepAlive()); return false; } @@ -365,7 +361,7 @@ namespace Mist{ bool InputRTSP::handleUDP(){ if (TCPmode){return false;} bool r = false; - for (std::map::iterator it = sdpState.tracks.begin(); + for (std::map::iterator it = sdpState.tracks.begin(); it != sdpState.tracks.end(); ++it){ Socket::UDPConnection &s = it->second.data; it->second.sorter.setCallback(it->first, insertRTP); @@ -380,14 +376,29 @@ namespace Mist{ if (!it->second.theirSSRC){it->second.theirSSRC = pack.getSSRC();} it->second.sorter.addPacket(pack); } - if (Util::epoch() / 5 != it->second.rtcpSent){ - it->second.rtcpSent = Util::epoch() / 5; - it->second.pack.sendRTCP_RR(connectedAt, it->second, it->first, myMeta, sendUDP); + if (Util::bootSecs() != it->second.rtcpSent){ + it->second.rtcpSent = Util::bootSecs(); + it->second.pack.sendRTCP_RR(it->second, sendUDP); } } return r; } - void InputRTSP::incoming(const DTSC::Packet &pkt){nProxy.bufferLivePacket(pkt, myMeta);} + void InputRTSP::incoming(const DTSC::Packet &pkt){ + if (!M.getBootMsOffset()){ + meta.setBootMsOffset(Util::bootMS() - pkt.getTime()); + packetOffset = 0; + setPacketOffset = true; + }else if (!setPacketOffset){ + packetOffset = (Util::bootMS() - pkt.getTime()) - M.getBootMsOffset(); + setPacketOffset = true; + } + static DTSC::Packet newPkt; + char *pktData; + size_t pktDataLen; + pkt.getString("data", pktData, pktDataLen); + bufferLivePacket(pkt.getTime() + packetOffset, pkt.getInt("offset"), pkt.getTrackId(), pktData, + pktDataLen, 0, pkt.getFlag("keyframe")); + } }// namespace Mist diff --git a/src/input/input_rtsp.h b/src/input/input_rtsp.h index 757cefe5..e228c981 100644 --- a/src/input/input_rtsp.h +++ b/src/input/input_rtsp.h @@ -22,11 +22,9 @@ namespace Mist{ bool checkArguments(); bool needHeader(){return false;} bool readHeader(){return true;} - void getNext(bool smart = true){} bool openStreamSource(); void closeStreamSource(); void parseStreamHeader(); - void seek(int seekTime){} void sendCommand(const std::string &cmd, const std::string &cUrl, const std::string &body, const std::map *extraHeaders = 0, bool reAuth = true); bool parsePacket(bool mustHave = false); @@ -43,8 +41,10 @@ namespace Mist{ bool TCPmode; bool needAuth; std::string session; - long long connectedAt; ///< The timestamp the connection was made, as reference point for RTCP - /// packets. + bool setPacketOffset; + int64_t packetOffset; + + std::string lastRequestedSetup; }; }// namespace Mist diff --git a/src/input/input_srt.cpp b/src/input/input_srt.cpp index 999a4396..b89fa32e 100644 --- a/src/input/input_srt.cpp +++ b/src/input/input_srt.cpp @@ -47,25 +47,23 @@ namespace Mist{ bool InputSrt::readHeader(){ if (!fileSource.good()){return false;} - - myMeta.tracks[1].trackID = 1; - myMeta.tracks[1].type = "meta"; - myMeta.tracks[1].codec = "subtitle"; + size_t idx = meta.addTrack(); + meta.setID(idx, 1); + meta.setType(idx, "meta"); + meta.setCodec(idx, "subtitle"); getNext(); while (thisPacket){ - myMeta.update(thisPacket); + meta.update(thisPacket); getNext(); } // outputting dtsh file - myMeta.toFile(config->getString("input") + ".dtsh"); + M.toFile(config->getString("input") + ".dtsh"); return true; } - void InputSrt::getNext(bool smart){ - bool hasPacket = false; - + void InputSrt::getNext(size_t idx){ thisPacket.null(); std::string line; @@ -93,7 +91,7 @@ namespace Mist{ static JSON::Value thisPack; thisPack.null(); thisPack["trackid"] = 1; - thisPack["bpos"] = (uint64_t)fileSource.tellg(); + thisPack["bpos"] = fileSource.tellg(); thisPack["data"] = data; thisPack["index"] = index; thisPack["time"] = timestamp; @@ -133,12 +131,6 @@ namespace Mist{ thisPacket.null(); } - void InputSrt::seek(int seekTime){fileSource.seekg(0, fileSource.beg);} - - void InputSrt::trackSelect(std::string trackSpec){ - // we only have one track.. - selectedTracks.clear(); - selectedTracks.insert(1); - } + void InputSrt::seek(uint64_t seekTime, size_t idx){fileSource.seekg(0, fileSource.beg);} }// namespace Mist diff --git a/src/input/input_srt.h b/src/input/input_srt.h index 5304b19d..93dfad78 100644 --- a/src/input/input_srt.h +++ b/src/input/input_srt.h @@ -16,9 +16,8 @@ namespace Mist{ bool checkArguments(); bool readHeader(); bool preRun(); - void getNext(bool smart = true); - void seek(int seekTime); - void trackSelect(std::string trackSpec); + void getNext(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); bool vtt; FILE *inFile; diff --git a/src/input/input_ts.cpp b/src/input/input_ts.cpp index f4de158e..e2ff6134 100644 --- a/src/input/input_ts.cpp +++ b/src/input/input_ts.cpp @@ -27,82 +27,112 @@ TS::Stream liveStream(true); Util::Config *cfgPointer = NULL; #define THREAD_TIMEOUT 15 -std::map threadTimer; +std::map threadTimer; -std::set claimableThreads; +std::set claimableThreads; -void parseThread(void *ignored){ +void parseThread(void *mistIn){ + Mist::inputTS *input = reinterpret_cast(mistIn); - int tid = -1; + size_t tid = 0; { tthread::lock_guard guard(threadClaimMutex); if (claimableThreads.size()){ tid = *claimableThreads.begin(); claimableThreads.erase(claimableThreads.begin()); } - if (tid == -1){return;} } + if (tid == 0){return;} - Mist::negotiationProxy myProxy; - myProxy.streamName = globalStreamName; - DTSC::Meta myMeta; + Comms::Users userConn; + DTSC::Meta meta; - if (liveStream.isDataTrack(tid)){ + bool dataTrack = liveStream.isDataTrack(tid); + + if (dataTrack){ if (!Util::streamAlive(globalStreamName) && !Util::startInput(globalStreamName, "push://INTERNAL_ONLY:" + cfgPointer->getString("input"), true, true)){ FAIL_MSG("Could not start buffer for %s", globalStreamName.c_str()); return; } - char userPageName[NAME_BUFFER_SIZE]; - snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, globalStreamName.c_str()); - myProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); - myProxy.userClient.countAsViewer = false; + { + tthread::lock_guard guard(threadClaimMutex); + if (!input->hasMeta()){input->reloadClientMeta();} + } + meta.reInit(globalStreamName, false); } + size_t idx = meta.trackIDToIndex(tid, getpid()); + threadTimer[tid] = Util::bootSecs(); while (Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT && cfgPointer->is_active && - (!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())){ + (!liveStream.isDataTrack(tid) || (userConn ? userConn.isAlive() : true))){ { tthread::lock_guard guard(threadClaimMutex); threadTimer[tid] = Util::bootSecs(); } - if (liveStream.isDataTrack(tid)){myProxy.userClient.keepAlive();} + if (liveStream.isDataTrack(tid)){userConn.keepAlive();} liveStream.parse(tid); if (!liveStream.hasPacket(tid)){ Util::sleep(100); continue; } uint64_t startSecs = Util::bootSecs(); - while (liveStream.hasPacket(tid) && ((Util::bootSecs() < startSecs + 2) && cfgPointer->is_active && - (!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive()))){ - liveStream.initializeMetadata(myMeta, tid); - DTSC::Packet pack; - liveStream.getPacket(tid, pack); - if (!pack){ - Util::sleep(100); - break; + while (liveStream.hasPacket(tid) && + ((Util::bootSecs() < startSecs + 2) && cfgPointer->is_active && + (!liveStream.isDataTrack(tid) || (userConn ? userConn.isAlive() : true)))){ + liveStream.parse(tid); + if (liveStream.hasPacket(tid)){ + if (idx == INVALID_TRACK_ID){ + tthread::lock_guard guard(threadClaimMutex); + liveStream.initializeMetadata(meta, tid); + idx = meta.trackIDToIndex(tid, getpid()); + if (idx != INVALID_TRACK_ID){ + userConn.reload(globalStreamName, idx, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK); + input->reloadClientMeta(); + } + } + if (idx == INVALID_TRACK_ID || !meta.trackValid(idx)){continue;} + if (!meta.trackLoaded(idx)){meta.refresh();} + DTSC::Packet pack; + liveStream.getPacket(tid, pack); + if (pack){ + tthread::lock_guard guard(threadClaimMutex); + if (!input->hasMeta()){input->reloadClientMeta();} + if (dataTrack){ + char *data; + size_t dataLen; + pack.getString("data", data, dataLen); + input->bufferLivePacket(pack.getTime(), pack.getInt("offset"), idx, data, dataLen, + pack.getInt("bpos"), pack.getFlag("keyframe")); + } + } } - if (myMeta.tracks.count(tid)){ - myProxy.continueNegotiate(tid, myMeta, true); - myProxy.bufferLivePacket(pack, myMeta); + { + tthread::lock_guard guard(threadClaimMutex); + threadTimer[tid] = Util::bootSecs(); + } + if (!liveStream.hasPacket(tid)){ + if (liveStream.isDataTrack(tid)){userConn.keepAlive();} + Util::sleep(100); } } } std::string reason = "unknown reason"; if (!(Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT)){reason = "thread timeout";} if (!cfgPointer->is_active){reason = "input shutting down";} - if (!(!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())){ + if (!(!liveStream.isDataTrack(tid) || userConn.isAlive())){ reason = "buffer disconnect"; cfgPointer->is_active = false; } - INFO_MSG("Shutting down thread for %d because %s", tid, reason.c_str()); + INFO_MSG("Shutting down thread for %zu because %s", tid, reason.c_str()); { tthread::lock_guard guard(threadClaimMutex); threadTimer.erase(tid); } liveStream.eraseTrack(tid); - myProxy.userClient.finish(); + if (dataTrack && userConn){userConn.setStatus(COMM_STATUS_DISCONNECT);} } namespace Mist{ @@ -117,6 +147,7 @@ namespace Mist{ "standard input (ts-exec:*), or multicast/unicast UDP sockets (tsudp://*)."; capa["source_match"].append("/*.ts"); capa["source_file"] = "$source"; + capa["source_match"].append("/*.m2ts"); capa["source_match"].append("stream://*.ts"); capa["source_match"].append("tsudp://*"); capa["source_match"].append("ts-exec:*"); @@ -143,9 +174,9 @@ namespace Mist{ capa["codecs"][0u][1u].append("MP2"); inFile = NULL; inputProcess = 0; + isFinished = false; { - int fin = 0, fout = 0, ferr = 0; pid_t srt_tx = -1; const char *args[] ={"srt-live-transmit", 0}; srt_tx = Util::Procs::StartPiped(args, 0, 0, 0); @@ -265,23 +296,6 @@ namespace Mist{ return inFile; } - /// Track selector of TS Input - ///\arg trackSpec specifies which tracks are to be selected - ///\todo test whether selecting a subset of tracks work - void inputTS::trackSelect(std::string trackSpec){ - selectedTracks.clear(); - long long int 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 = ""; - } - } - } - bool inputTS::needHeader(){ if (!standAlone){return false;} return Input::needHeader(); @@ -295,6 +309,7 @@ namespace Mist{ ///\todo Find errors, perhaps parts can be made more modular bool inputTS::readHeader(){ if (!inFile){return false;} + meta.reInit(streamName); TS::Packet packet; // to analyse and extract data DTSC::Packet headerPack; fseek(inFile, 0, SEEK_SET); // seek to beginning @@ -306,28 +321,39 @@ namespace Mist{ if (packet.getUnitStart()){ while (tsStream.hasPacketOnEachTrack()){ tsStream.getEarliestPacket(headerPack); - if (!headerPack){break;} - if (!myMeta.tracks.count(headerPack.getTrackId()) || - !myMeta.tracks[headerPack.getTrackId()].codec.size()){ - tsStream.initializeMetadata(myMeta, headerPack.getTrackId()); + size_t pid = headerPack.getTrackId(); + size_t idx = M.trackIDToIndex(pid, getpid()); + if (idx == INVALID_TRACK_ID || !M.getCodec(idx).size()){ + tsStream.initializeMetadata(meta, pid); + idx = M.trackIDToIndex(pid, getpid()); } - myMeta.update(headerPack); + char *data; + size_t dataLen; + headerPack.getString("data", data, dataLen); + meta.update(headerPack.getTime(), headerPack.getInt("offset"), idx, dataLen, + headerPack.getInt("bpos"), headerPack.getFlag("keyframe"), headerPack.getDataLen()); } } } tsStream.finish(); - INFO_MSG("Reached %s at %llu bytes", feof(inFile) ? "EOF" : "error", lastBpos); + INFO_MSG("Reached %s at %" PRIu64 " bytes", feof(inFile) ? "EOF" : "error", lastBpos); while (tsStream.hasPacket()){ tsStream.getEarliestPacket(headerPack); - if (!myMeta.tracks.count(headerPack.getTrackId()) || - !myMeta.tracks[headerPack.getTrackId()].codec.size()){ - tsStream.initializeMetadata(myMeta, headerPack.getTrackId()); + size_t pid = headerPack.getTrackId(); + size_t idx = M.trackIDToIndex(pid, getpid()); + if (idx == INVALID_TRACK_ID || !M.getCodec(idx).size()){ + tsStream.initializeMetadata(meta, pid); + idx = M.trackIDToIndex(pid, getpid()); } - myMeta.update(headerPack); + char *data; + size_t dataLen; + headerPack.getString("data", data, dataLen); + meta.update(headerPack.getTime(), headerPack.getInt("offset"), idx, dataLen, + headerPack.getInt("bpos"), headerPack.getFlag("keyframe"), headerPack.getDataLen()); } fseek(inFile, 0, SEEK_SET); - myMeta.toFile(config->getString("input") + ".dtsh"); + meta.toFile(config->getString("input") + ".dtsh"); return true; } @@ -335,40 +361,42 @@ namespace Mist{ /// At the moment, the logic of sending the last packet that was finished has been implemented, /// but the seeking and finding data is not yet ready. ///\todo Finish the implementation - void inputTS::getNext(bool smart){ - INSANE_MSG("Getting next"); + void inputTS::getNext(size_t idx){ + size_t pid = (idx == INVALID_TRACK_ID ? 0 : M.getID(idx)); + INSANE_MSG("Getting next on track %zu", idx); thisPacket.null(); - bool hasPacket = - (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacket()); + bool hasPacket = (idx == INVALID_TRACK_ID ? tsStream.hasPacket() : tsStream.hasPacket(pid)); while (!hasPacket && !feof(inFile) && (inputProcess == 0 || Util::Procs::childRunning(inputProcess)) && config->is_active){ tsBuf.FromFile(inFile); - if (selectedTracks.count(tsBuf.getPID())){ + if (idx == INVALID_TRACK_ID || pid == tsBuf.getPID()){ tsStream.parse(tsBuf, 0); // bPos == 0 if (tsBuf.getUnitStart()){ - hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) - : tsStream.hasPacket()); + hasPacket = (idx == INVALID_TRACK_ID ? tsStream.hasPacket() : tsStream.hasPacket(pid)); } } } if (feof(inFile)){ - tsStream.finish(); + if (!isFinished){ + tsStream.finish(); + isFinished = true; + } hasPacket = true; } if (!hasPacket){return;} - if (selectedTracks.size() == 1){ - if (tsStream.hasPacket(*selectedTracks.begin())){ - tsStream.getPacket(*selectedTracks.begin(), thisPacket); - } - }else{ + if (idx == INVALID_TRACK_ID){ if (tsStream.hasPacket()){tsStream.getEarliestPacket(thisPacket);} + }else{ + if (tsStream.hasPacket(pid)){tsStream.getPacket(pid, thisPacket);} } + if (!thisPacket){ INFO_MSG("Could not getNext TS packet!"); return; } - tsStream.initializeMetadata(myMeta); - if (!myMeta.tracks.count(thisPacket.getTrackId())){getNext();} + tsStream.initializeMetadata(meta); + size_t thisIdx = M.trackIDToIndex(thisPacket.getTrackId(), getpid()); + if (thisIdx == INVALID_TRACK_ID){getNext(idx);} } void inputTS::readPMT(){ @@ -388,24 +416,31 @@ namespace Mist{ tsStream.partialClear(); // Restore original file position - if (Util::fseek(inFile, bpos, SEEK_SET)){return;} + if (Util::fseek(inFile, bpos, SEEK_SET)){ + clearerr(inFile); + return; + } } /// Seeks to a specific time - void inputTS::seek(int seekTime){ + void inputTS::seek(uint64_t seekTime, size_t idx){ tsStream.clear(); readPMT(); - uint64_t seekPos = 0xFFFFFFFFFFFFFFFFull; - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - unsigned long thisBPos = 0; - for (std::deque::iterator keyIt = myMeta.tracks[*it].keys.begin(); - keyIt != myMeta.tracks[*it].keys.end(); keyIt++){ - if (keyIt->getTime() > seekTime){break;} - thisBPos = keyIt->getBpos(); - tsStream.setLastms(*it, keyIt->getTime()); + uint64_t seekPos = 0xFFFFFFFFull; + if (idx != INVALID_TRACK_ID){ + DTSC::Keys keys(M.keys(idx)); + uint32_t keyNum = keys.getNumForTime(seekTime); + seekPos = keys.getBpos(keyNum); + }else{ + std::set tracks = M.getValidTracks(); + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++){ + DTSC::Keys keys(M.keys(*it)); + uint32_t keyNum = keys.getNumForTime(seekTime); + uint64_t thisBPos = keys.getBpos(keyNum); + if (thisBPos < seekPos){seekPos = thisBPos;} } - if (thisBPos < seekPos){seekPos = thisBPos;} } + clearerr(inFile); Util::fseek(inFile, seekPos, SEEK_SET); // seek to the correct position } @@ -424,22 +459,23 @@ namespace Mist{ } void inputTS::parseStreamHeader(){ - // Placeholder to force normal code to continue despite no tracks available - myMeta.tracks[0].type = "audio"; + // Placeholder empty track to force normal code to continue despite no tracks available + tmpIdx = meta.addTrack(0, 0, 0, 0); } std::string inputTS::streamMainLoop(){ - myMeta.tracks.clear(); // wipe the placeholder track from above - IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true); + meta.removeTrack(tmpIdx); + INFO_MSG("Removed temptrack %zu", tmpIdx); + Comms::Statistics statComm; uint64_t downCounter = 0; - uint64_t startTime = Util::epoch(); + uint64_t startTime = Util::bootSecs(); uint64_t noDataSince = Util::bootSecs(); bool gettingData = false; bool hasStarted = false; cfgPointer = config; globalStreamName = streamName; unsigned long long threadCheckTimer = Util::bootSecs(); - while (config->is_active && nProxy.userClient.isAlive()){ + while (config->is_active){ if (tcpCon){ if (tcpCon.spool()){ while (tcpCon.Received().available(188)){ @@ -471,7 +507,7 @@ namespace Mist{ gettingData = true; INFO_MSG("Now receiving UDP data..."); } - int offset = 0; + size_t offset = 0; // Try to read full TS Packets // Watch out! We push here to a global, in order for threads to be able to access it. while (offset < udpCon.data_len){ @@ -489,7 +525,7 @@ namespace Mist{ uint32_t maxBytes = std::min((uint32_t)(188 - leftData.size()), (uint32_t)(udpCon.data_len - offset)); uint32_t numBytes = maxBytes; - VERYHIGH_MSG("%lu bytes of non-sync-byte data received", numBytes); + VERYHIGH_MSG("%" PRIu32 " bytes of non-sync-byte data received", numBytes); if (leftData.size()){ leftData.append(udpCon.data + offset, numBytes); while (leftData.size() >= 188){ @@ -517,28 +553,23 @@ namespace Mist{ // Check for and spawn threads here. if (Util::bootSecs() - threadCheckTimer > 1){ // Connect to stats for INPUT detection - uint64_t now = Util::epoch(); - if (!statsPage.getData()){ - statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true); - } - if (statsPage.getData()){ - if (!statsPage.isAlive()){ + statComm.reload(); + if (statComm){ + if (!statComm.isAlive()){ config->is_active = false; - statsPage.finish(); return "received shutdown request from controller"; } - IPC::statExchange tmpEx(statsPage.getData()); - tmpEx.now(now); - tmpEx.crc(getpid()); - tmpEx.streamName(streamName); - tmpEx.connector("INPUT"); - tmpEx.up(0); - tmpEx.down(downCounter + tcpCon.dataDown()); - tmpEx.time(now - startTime); - tmpEx.lastSecond(0); - statsPage.keepAlive(); + uint64_t now = Util::bootSecs(); + statComm.setNow(now); + statComm.setCRC(getpid()); + statComm.setStream(streamName); + statComm.setConnector("INPUT"); + statComm.setUp(0); + statComm.setDown(downCounter + tcpCon.dataDown()); + statComm.setTime(now - startTime); + statComm.setLastSecond(0); + statComm.keepAlive(); } - nProxy.userClient.keepAlive(); std::set activeTracks = liveStream.getActiveTracks(); { @@ -546,7 +577,6 @@ namespace Mist{ if (hasStarted && !threadTimer.size()){ if (!isAlwaysOn()){ config->is_active = false; - statsPage.finish(); return "no active threads and we had input in the past"; }else{ hasStarted = false; @@ -555,7 +585,8 @@ namespace Mist{ for (std::set::iterator it = activeTracks.begin(); it != activeTracks.end(); it++){ if (!liveStream.isDataTrack(*it)){continue;} if (threadTimer.count(*it) && ((Util::bootSecs() - threadTimer[*it]) > (2 * THREAD_TIMEOUT))){ - WARN_MSG("Thread for track %d timed out %d seconds ago without a clean shutdown.", + WARN_MSG("Thread for track %" PRIu64 " timed out %" PRIu64 + " seconds ago without a clean shutdown.", *it, Util::bootSecs() - threadTimer[*it]); threadTimer.erase(*it); } @@ -566,7 +597,7 @@ namespace Mist{ claimableThreads.insert(*it); // Spawn thread here. - tthread::thread thisThread(parseThread, 0); + tthread::thread thisThread(parseThread, this); thisThread.detach(); } } @@ -576,14 +607,12 @@ namespace Mist{ if (Util::bootSecs() - noDataSince > 20){ if (!isAlwaysOn()){ config->is_active = false; - statsPage.finish(); return "No packets received for 20 seconds - terminating"; }else{ noDataSince = Util::bootSecs(); } } } - statsPage.finish(); return "received shutdown request"; } @@ -612,9 +641,8 @@ namespace Mist{ inpt.substr(0, 7) != "http://" && inpt.substr(0, 10) != "http-ts://" && inpt.substr(0, 8) != "https://" && inpt.substr(0, 11) != "https-ts://"){ return Input::needsLock(); - }else{ - return false; } + return false; } }// namespace Mist diff --git a/src/input/input_ts.h b/src/input/input_ts.h index f2d4ecac..bb307fc1 100644 --- a/src/input/input_ts.h +++ b/src/input/input_ts.h @@ -20,9 +20,8 @@ namespace Mist{ bool preRun(); bool readHeader(); bool needHeader(); - void getNext(bool smart = true); - void seek(int seekTime); - void trackSelect(std::string trackSpec); + void getNext(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); void readPMT(); bool openStreamSource(); void parseStreamHeader(); @@ -34,6 +33,8 @@ namespace Mist{ Socket::Connection tcpCon; TS::Packet tsBuf; pid_t inputProcess; + size_t tmpIdx; + bool isFinished; }; }// namespace Mist diff --git a/src/io.cpp b/src/io.cpp index 31b7dcd3..0143392d 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -3,94 +3,29 @@ #include #include #include +#include #include +#include //LTS #include namespace Mist{ - /// Opens a shared memory page for the stream metadata. - /// - /// Assumes myMeta contains the metadata to write. - void InOutBase::initiateMeta(){ - VERYHIGH_MSG("initiateMeta for stream %s", streamName.c_str()); - // Open the page for the metadata - char pageName[NAME_BUFFER_SIZE]; - snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); - if (myMeta.live){ - nProxy.metaPages[0].init(pageName, DEFAULT_STRM_PAGE_SIZE, true); - }else{ - nProxy.metaPages[0].init(pageName, myMeta.getSendLen(), true); - } - // Make sure we don't delete it on accident - nProxy.metaPages[0].master = false; - - // Write the metadata to the page - myMeta.writeTo(nProxy.metaPages[0].mapped); - } + InOutBase::InOutBase() : M(meta){} /// Returns the ID of the main selected track, or 0 if no tracks are selected. /// The main track is the first video track, if any, and otherwise the first other track. - long unsigned int InOutBase::getMainSelectedTrack(){ - if (!selectedTracks.size()){return 0;} - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - if (myMeta.tracks.count(*it) && myMeta.tracks[*it].type == "video"){return *it;} - } - return *(selectedTracks.begin()); - } - - void negotiationProxy::clear(){ - pagesByTrack.clear(); - trackOffset.clear(); - trackState.clear(); - trackMap.clear(); - metaPages.clear(); - curPageNum.clear(); - curPage.clear(); - negTimer = 0; - userClient.finish(); - } - - /*LTS-START*/ - void negotiationProxy::initiateEncryption(){ - static bool encInit = false; - if (encInit){return;} - encrypt = false; - JSON::Value cfg = Util::getStreamConfig(streamName); - vmData.url = cfg.isMember("verimatrix-playready") ? cfg["verimatrix-playready"].asString() : ""; - vmData.name = streamName; - if (vmData.url != ""){ - Encryption::fillVerimatrix(vmData); - }else{ - vmData.keyid = cfg.isMember("keyid") ? cfg["keyid"].asString() : ""; - vmData.keyseed = cfg.isMember("keyseed") ? cfg["keyseed"].asString() : ""; - if (vmData.keyid != "" && vmData.keyseed != ""){ - vmData.keyid = Encodings::Base64::decode(vmData.keyid); - vmData.keyseed = Encodings::Base64::decode(vmData.keyseed); - vmData.key = Encryption::PR_GenerateContentKey(vmData.keyseed, vmData.keyid); - vmData.laurl = cfg.isMember("la_url") ? cfg["la_url"].asString() : ""; + /// Returns INVALID_TRACK_ID if there are no valid selected tracks. + /// Refreshes the metadata to make sure we don't return unloaded tracks. + size_t InOutBase::getMainSelectedTrack(){ + if (!userSelect.size()){return INVALID_TRACK_ID;} + size_t bestSoFar = INVALID_TRACK_ID; + meta.refresh(); + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (meta.trackValid(it->first)){ + if (meta.getType(it->first) == "video"){return it->first;} + bestSoFar = it->first; } } - if (vmData.key != ""){ - encrypt = true; - char pageName[NAME_BUFFER_SIZE]; - snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_ENCRYPT, streamName.c_str()); - encryptionPage.init(pageName, 8 * 1024 * 1024, true); - vmData.write(encryptionPage.mapped); - } - encInit = true; - } - /*LTS-END*/ - - bool InOutBase::bufferStart(unsigned long tid, unsigned long pageNumber){ - VERYHIGH_MSG("bufferStart for stream %s, track %lu, page %lu", streamName.c_str(), tid, pageNumber); - // Initialize the stream metadata if it does not yet exist -#ifndef TSLIVE_INPUT - if (!nProxy.metaPages.count(0)){initiateMeta();} -#endif - // If we are a stand-alone player skip track negotiation, as there will be nothing to negotiate with. - if (standAlone){ - if (!nProxy.trackMap.count(tid)){nProxy.trackMap[tid] = tid;} - } - return nProxy.bufferStart(tid, pageNumber, myMeta); + return bestSoFar; } /// Starts the buffering of a new page. @@ -100,56 +35,42 @@ namespace Mist{ /// Buffering itself is done by bufferNext(). ///\param tid The trackid of the page to start buffering ///\param pageNumber The number of the page to start buffering - bool negotiationProxy::bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta &myMeta){ - initiateEncryption(); - // Negotiate the requested track if needed. - continueNegotiate(tid, myMeta); + bool InOutBase::bufferStart(size_t idx, size_t pageNumber){ + VERYHIGH_MSG("bufferStart for stream %s, track %zu, page %zu", streamName.c_str(), idx, pageNumber); + // Initialize the stream metadata if it does not yet exist +#ifndef TSLIVE_INPUT + if (!meta){meta.reInit(streamName);} +#endif - // If the negotation state for this track is not 'Accepted', stop buffering this page, maybe try again later. - if (trackState[tid] != FILL_ACC){ - ///\return false if the track has not been accepted (yet) + if (!meta.getValidTracks().size()){ + meta.clear(); return false; } - // If the track is accepted, we will have a mapped tid - unsigned long mapTid = trackMap[tid]; - - // Before we start a new page, make sure we can be heard by the buffer about this. - // Otherwise, it might linger forever as a nasty data leak. - // Nobody likes nasty data leaks. - { - char pageName[NAME_BUFFER_SIZE]; - snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), mapTid); - IPC::sharedPage checkPage(pageName, SHM_TRACK_INDEX_SIZE, false, false); - if (!checkPage.mapped){ - WARN_MSG("Buffer deleted %s@%lu (%s) index. Re-negotiating...", streamName.c_str(), mapTid, - myMeta.tracks[tid].codec.c_str()); - trackState.erase(tid); - trackMap.erase(tid); - trackOffset.erase(tid); - pagesByTrack.erase(tid); - metaPages.erase(tid); - curPageNum.erase(tid); - curPage.erase(tid); - return bufferStart(tid, pageNumber, myMeta); - } - } // If we are currently buffering a page, abandon it completely and print a message about this // This page will NEVER be deleted, unless we open it again later. - if (curPage.count(tid)){ - WARN_MSG("Abandoning current page (%lu) for track %lu~>%lu", curPageNum[tid], tid, mapTid); - curPage.erase(tid); - curPageNum.erase(tid); + if (curPage.count(idx)){ + WARN_MSG("Abandoning current page (%zu) for track %zu", curPageNum[idx], idx); + curPage.erase(idx); + curPageNum.erase(idx); + } + + Util::RelAccX &tPages = meta.pages(idx); + + size_t pageIdx = INVALID_PAGE_NUM; + for (size_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ + if (tPages.getInt("firstkey", i) == pageNumber){ + pageIdx = i; + break; + } } // If this is not a valid page number on this track, stop buffering this page. - if (!pagesByTrack[tid].count(pageNumber)){ - WARN_MSG("Aborting page buffer start: %lu is not a valid page number on track %lu~>%lu.", - pageNumber, tid, mapTid); + if (pageIdx == INVALID_PAGE_NUM){ + WARN_MSG("Aborting page buffer start: %zu is not a valid page number on track %zu.", pageNumber, idx); std::stringstream test; - for (std::map::iterator it = pagesByTrack[tid].begin(); - it != pagesByTrack[tid].end(); it++){ - test << it->first << " "; + for (size_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ + test << tPages.getInt("firstkey", i) << " "; } INFO_MSG("Valid page numbers: %s", test.str().c_str()); ///\return false if the pagenumber is not valid for this track @@ -157,57 +78,27 @@ namespace Mist{ } // If the page is already buffered, ignore this request - if (isBuffered(tid, pageNumber)){ - INFO_MSG("Page %lu on track %lu~>%lu already buffered", pageNumber, tid, mapTid); + if (isBuffered(idx, pageNumber)){ + INFO_MSG("Page %zu on track %zu already buffered", pageNumber, idx); ///\return false if the page was already buffered. return false; } // Open the correct page for the data char pageId[NAME_BUFFER_SIZE]; - snprintf(pageId, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), mapTid, pageNumber); - int pageSize = pagesByTrack[tid][pageNumber].dataSize; - /*LTS-START*/ - if (encrypt){ - pageSize += pageSize >> 1; - HIGH_MSG("Page size X1.5 = %d", pageSize); - }else{ - HIGH_MSG("Page size %d", pageSize); - } - /*LTS-END*/ + snprintf(pageId, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), idx, pageNumber); + uint64_t pageSize = tPages.getInt("size", pageIdx); std::string pageName(pageId); - curPage[tid].init(pageName, pageSize, true); + curPage[idx].init(pageName, pageSize, true); // Make sure the data page is not destroyed when we are done buffering it later on. - curPage[tid].master = false; + curPage[idx].master = false; // Store the pagenumber of the currently buffer page - curPageNum[tid] = pageNumber; + curPageNum[idx] = pageNumber; - // Initialize the bookkeeping entry, and set the current offset to 0, to allow for using it in bufferNext() - pagesByTrack[tid][pageNumber].curOffset = 0; + // Set the current offset to 0, to allow for using it in bufferNext() + tPages.setInt("avail", 0, pageIdx); - HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid); - - if (myMeta.live){ - // Register this page on the meta page - // NOTE: It is important that this only happens if the stream is live.... - bool inserted = false; - for (int i = 0; i < 1024; i++){ - char *tmpOffset = metaPages[tid].mapped + (i * 8); - if ((Bit::btohl(tmpOffset) == 0 && Bit::btohl(tmpOffset + 4) == 0)){ - Bit::htobl(tmpOffset, curPageNum[tid]); - Bit::htobl(tmpOffset + 4, 1000); - inserted = true; - break; - } - } - if (!inserted){ - FAIL_MSG("Could not insert page in track index. Aborting."); - curPage[tid].master = true; // set this page for instant-deletion when we're done with it - return false; - } - } - - ///\return true if everything was successful + HIGH_MSG("Start buffering page %zu on track %zu successful", pageNumber, idx); return true; } @@ -216,48 +107,37 @@ namespace Mist{ /// Does not do anything if the process is not standalone, in this case the master process will have an overloaded version of this function. ///\param tid The trackid to remove the page from ///\param pageNumber The number of the page to remove - void InOutBase::bufferRemove(unsigned long tid, unsigned long pageNumber){ - if (!standAlone){ - // A different process will handle this for us + void InOutBase::bufferRemove(size_t idx, size_t pageNumber){ + if (!standAlone){// A different process will handle this for us return; } - unsigned long mapTid = nProxy.trackMap[tid]; + Util::RelAccX &tPages = meta.pages(idx); - DEBUG_MSG(DLVL_HIGH, "Removing page %lu on track %lu~>%lu from the corresponding metaPage", - pageNumber, tid, mapTid); - int i = 0; - for (; i < 1024; i++){ - char *tmpOffset = nProxy.metaPages[tid].mapped + (i * 8); - if (Bit::btohl(tmpOffset) == pageNumber){ - Bit::htobl(tmpOffset, 0); - Bit::htobl(tmpOffset + 4, 0); + size_t pageIdx = INVALID_PAGE_NUM; + for (size_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ + if (tPages.getInt("firstkey", i) == pageNumber){ + pageIdx = i; break; } } - if (i == 1024){ - ERROR_MSG("Could not erase page %lu for track %lu->%lu stream %s from track index!", - pageNumber, tid, mapTid, streamName.c_str()); + // If the given pagenumber is not a valid page on this track, do nothing + if (pageIdx == INVALID_PAGE_NUM){ + INFO_MSG("Can't remove page %zu on track %zu as it is not a valid page number.", pageNumber, idx); + return; } - if (!nProxy.pagesByTrack.count(tid)){ - // If there is no pagesByTrack entry, the pages are managed in local code and not through io.cpp (e.g.: MistInBuffer) - return; - } - // If the given pagenumber is not a valid page on this track, do nothing - if (!nProxy.pagesByTrack[tid].count(pageNumber)){ - INFO_MSG("Can't remove page %lu on track %lu~>%lu as it is not a valid page number.", - pageNumber, tid, mapTid); - return; - } + HIGH_MSG("Removing page %zu on track %zu from the corresponding metaPage", pageNumber, idx); + tPages.setInt("avail", 0, pageIdx); + // Open the correct page char pageId[NAME_BUFFER_SIZE]; - snprintf(pageId, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), mapTid, pageNumber); + snprintf(pageId, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), idx, pageNumber); std::string pageName(pageId); IPC::sharedPage toErase; #ifdef __CYGWIN__ toErase.init(pageName, 26 * 1024 * 1024, false); #else - toErase.init(pageName, nProxy.pagesByTrack[tid][pageNumber].dataSize, false); + toErase.init(pageName, tPages.getInt("size", pageIdx), false); #endif // Set the master flag so that the page will be destroyed once it leaves scope #if defined(__CYGWIN__) || defined(_WIN32) @@ -271,186 +151,149 @@ namespace Mist{ /// Checks whether a key is buffered ///\param tid The trackid on which to locate the key ///\param keyNum The number of the keyframe to find - bool negotiationProxy::isBuffered(unsigned long tid, unsigned long keyNum){ + bool InOutBase::isBuffered(size_t idx, uint32_t keyNum){ ///\return The result of bufferedOnPage(tid, keyNum) - return bufferedOnPage(tid, keyNum); + return bufferedOnPage(idx, keyNum) != INVALID_KEY_NUM; } /// Returns the pagenumber where this key is buffered on ///\param tid The trackid on which to locate the key ///\param keyNum The number of the keyframe to find - unsigned long negotiationProxy::bufferedOnPage(unsigned long tid, unsigned long keyNum){ - // Check whether the track is accepted - if (!trackMap.count(tid) || !metaPages.count(tid) || !metaPages[tid].mapped){ - ///\return 0 if the page has not been mapped yet - return 0; + size_t InOutBase::bufferedOnPage(size_t idx, size_t keyNum){ + Util::RelAccX &tPages = meta.pages(idx); + + for (uint64_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ + uint64_t pageNum = tPages.getInt("firstkey", i); + if (pageNum > keyNum) break; + uint64_t keyCount = tPages.getInt("keycount", i); + if (pageNum + keyCount - 1 < keyNum) continue; + uint64_t avail = tPages.getInt("avail", i); + return avail ? pageNum : INVALID_KEY_NUM; } - // Loop over the index page - int len = metaPages[tid].len / 8; - for (int i = 0; i < len; ++i){ - char *tmpOffset = metaPages[tid].mapped + (i * 8); - unsigned int keyAmount = Bit::btohl(tmpOffset + 4); - if (keyAmount == 0){continue;} - // Check whether the key is on this page - unsigned int pageNum = Bit::btohl(tmpOffset); - if (pageNum <= keyNum && keyNum < pageNum + keyAmount){return pageNum;} - } - return 0; + return INVALID_KEY_NUM; } /// Buffers the next packet on the currently opened page ///\param pack The packet to buffer - void InOutBase::bufferNext(const DTSC::Packet &pack){nProxy.bufferNext(pack, myMeta);} + void InOutBase::bufferNext(uint64_t packTime, int64_t packOffset, uint32_t packTrack, const char *packData, + size_t packDataSize, uint64_t packBytePos, bool isKeyframe){ + size_t packDataLen = + 24 + (packOffset ? 17 : 0) + (packBytePos ? 15 : 0) + (isKeyframe ? 19 : 0) + packDataSize + 11; - void negotiationProxy::bufferNext(const DTSC::Packet &pack, DTSC::Meta &myMeta){ static bool multiWrong = false; // Save the trackid of the track for easier access - unsigned long tid = pack.getTrackId(); + if (packTrack == INVALID_TRACK_ID){ + WARN_MSG("Packet with id %" PRIu32 " has an invalid track", packTrack); + return; + } + // these checks were already done in bufferSinglePacket, but we check again just to be sure - if (myMeta.live && pack.getTime() < myMeta.tracks[tid].lastms){ - DEBUG_MSG(multiWrong ? DLVL_HIGH : DLVL_WARN, "Wrong order on track %lu ignored: %lu < %lu", - tid, pack.getTime(), myMeta.tracks[tid].lastms); + if (meta.getLive() && packTime < meta.getLastms(packTrack)){ + DEBUG_MSG(((multiWrong == 0) ? DLVL_WARN : DLVL_HIGH), + "Wrong order on track %" PRIu32 " ignored: %" PRIu64 " < %" PRIu64, packTrack, + packTime, meta.getLastms(packTrack)); multiWrong = true; return; } - unsigned long mapTid = trackMap[tid]; // Do nothing if no page is opened for this track - if (!curPage.count(tid)){ - INFO_MSG("Trying to buffer a packet on track %lu~>%lu, but no page is initialized", tid, mapTid); + if (!curPage.count(packTrack)){ + INFO_MSG("Trying to buffer a packet on track %" PRIu32 ", but no page is initialized", packTrack); return; } multiWrong = false; - IPC::sharedPage &myPage = curPage[tid]; - DTSCPageData &pageData = pagesByTrack[tid][curPageNum[tid]]; + IPC::sharedPage &myPage = curPage[packTrack]; + + Util::RelAccX &tPages = meta.pages(packTrack); + size_t pageIdx = 0; + for (uint64_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ + if (tPages.getInt("firstkey", i) == curPageNum[packTrack]){ + pageIdx = i; + break; + } + } // Save the current write position - size_t curOffset = pageData.curOffset; + uint64_t pageOffset = tPages.getInt("avail", pageIdx); + uint64_t pageSize = tPages.getInt("size", pageIdx); // Do nothing when there is not enough free space on the page to add the packet. - if (pageData.dataSize - curOffset < pack.getDataLen()){ - FAIL_MSG("Trying to buffer a packet (%llums) on page %lu for track %lu~>%lu, but we have a " - "size mismatch. The packet is %d bytes long, so won't fit at offset %lu on a page " - "of %llu bytes!", - pack.getTime(), curPageNum[tid], tid, mapTid, pack.getDataLen(), curOffset, pageData.dataSize); + if (pageSize - pageOffset < packDataLen){ + FAIL_MSG("Track %" PRIu32 "p%zu : Pack %" PRIu64 "ms of %" PRIu64 "b exceeds size %" PRIu64 " @ bpos %" PRIu64, + packTrack, curPageNum[packTrack], packTime, packDataLen, pageSize, pageOffset); return; } - // Brain melt starts here - char iVec[16]; - if (encrypt){ - if (iVecs.find(tid) == iVecs.end()){ - iVecs[tid] = ((long long unsigned int)rand() << 32) + rand(); - } - Bit::htobll(iVec, iVecs[tid]); - iVecs[tid]++; + // First generate only the payload on the correct destination + // Leaves the 20 bytes inbetween empty to ensure the data is not accidentally read before it is + // complete + char *data = myPage.mapped + pageOffset; - Encryption::encryptPlayReady((DTSC::Packet &)pack, myMeta.tracks[tid].codec, iVec, vmData.key.data()); + data[20] = 0xE0; // start container object + unsigned int offset = 21; + if (packOffset){ + memcpy(data + offset, "\000\006offset\001", 9); + Bit::htobll(data + offset + 9, packOffset); + offset += 17; } + if (packBytePos){ + memcpy(data + offset, "\000\004bpos\001", 7); + Bit::htobll(data + offset + 7, packBytePos); + offset += 15; + } + if (isKeyframe){ + memcpy(data + offset, "\000\010keyframe\001\000\000\000\000\000\000\000\001", 19); + offset += 19; + } + memcpy(data + offset, "\000\004data\002", 7); + Bit::htobl(data + offset + 7, packDataSize); + memcpy(data + offset + 11, packData ? packData : 0, packDataSize); + // finish container with 0x0000EE + memcpy(data + offset + 11 + packDataSize, "\000\000\356", 3); - // First memcpy only the payload to the destination - // Leaves the 20 bytes inbetween empty to ensure the data is not accidentally read before it is complete - memcpy(myPage.mapped + curOffset + 20, pack.getData() + 20, pack.getDataLen() - 20); - if (encrypt){ - // write ivec field + new object end at (currOffset + pack.getDataLen() - 3); - int ivecOffset = curOffset + pack.getDataLen() - 3; - memcpy(myPage.mapped + ivecOffset, "\000\004ivec\002\000\000\000\010", 11); - memcpy(myPage.mapped + ivecOffset + 11, iVec, 8); - // finish container with 0x0000EE - memcpy(myPage.mapped + ivecOffset + 19, "\000\000\356", 3); - } // Copy the remaining values in reverse order: // 8 byte timestamp - Bit::htobll(myPage.mapped + curOffset + 12, pack.getTime()); + Bit::htobll(myPage.mapped + pageOffset + 12, packTime); // The mapped track id - ((int *)(myPage.mapped + curOffset + 8))[0] = htonl(mapTid); - int size = Bit::btohl(pack.getData() + 4); - if (encrypt){ - // Alter size to reflect the addition of the ivec field ( + 19 ) - size += 19; - } + Bit::htobl(myPage.mapped + pageOffset + 8, packTrack); // Write the size - Bit::htobl(myPage.mapped + curOffset + 4, size); + Bit::htobl(myPage.mapped + pageOffset + 4, packDataLen - 8); // write the 'DTP2' bytes to conclude the packet and allow for reading it - memcpy(myPage.mapped + curOffset, pack.getData(), 4); + memcpy(myPage.mapped + pageOffset, "DTP2", 4); - if (myMeta.live){ - myMeta.update(pack); - // unsigned long cleanTrackID = pack.getTrackId(); - // myMeta.tracks[cleanTrackID].removeOldKeyframes(pack.getTime() - pack.timeOffset); + if (M.getLive()){ + meta.update(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe); } - // End of brain melt - pageData.curOffset += size + 8; + tPages.setInt("avail", pageOffset + packDataLen, pageIdx); } /// Wraps up the buffering of a shared memory data page /// /// Registers the data page on the track index page as well ///\param tid The trackid of the page to finalize - void InOutBase::bufferFinalize(unsigned long tid){nProxy.bufferFinalize(tid, myMeta);} - - void negotiationProxy::bufferFinalize(unsigned long tid, DTSC::Meta &myMeta){ - unsigned long mapTid = trackMap[tid]; + void InOutBase::bufferFinalize(size_t idx){ // If no page is open, do nothing - if (!curPage.count(tid)){ - INFO_MSG("Trying to finalize the current page on track %lu~>%lu, but no page is initialized", tid, mapTid); + if (!curPage.count(idx)){ + INFO_MSG("Trying to finalize the current page on track %zu, but no page is initialized", idx); return; } - // Keep track of registering the page on the track's index page - bool inserted = false; - int lowest = 0; - for (int i = 0; i < 1024; i++){ - char *tmpOffset = metaPages[tid].mapped + (i * 8); - int keyNum = Bit::btohl(tmpOffset); - int keyAmount = Bit::btohl(tmpOffset + 4); - if (!inserted){ - if (myMeta.live){ - if (keyNum == curPageNum[tid] && keyAmount == 1000){ - Bit::htobl(tmpOffset + 4, pagesByTrack[tid][curPageNum[tid]].keyNum); - inserted = true; - } - }else{ - // in case of vod, insert at the first "empty" spot - if (keyNum == 0){ - Bit::htobl(tmpOffset, curPageNum[tid]); - Bit::htobl(tmpOffset + 4, pagesByTrack[tid][curPageNum[tid]].keyNum); - inserted = true; - } - } - } - keyNum = Bit::btohl(tmpOffset); - if (!keyNum) continue; - if (!lowest || keyNum < lowest){lowest = keyNum;} - } - +/// \TODO META Re-Implement for Cygwin/Win32! #if defined(__CYGWIN__) || defined(_WIN32) static int wipedAlready = 0; if (lowest && lowest > wipedAlready + 1){ for (int curr = wipedAlready + 1; curr < lowest; ++curr){ char pageId[NAME_BUFFER_SIZE]; - snprintf(pageId, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), mapTid, curr); + snprintf(pageId, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), idx, curr); IPC::releasePage(std::string(pageId)); } } -#endif - // Print a message about registering the page or not. - if (!inserted){ - FAIL_MSG( - "Can't register %lu on the metaPage of %s track %lu~>%lu, No empty spots left. Deleting.", - curPageNum[tid], streamName.c_str(), tid, mapTid); - // Since the buffer can't see it - we should delete it ourselves, now. - curPage[tid].master = true; - }else{ - HIGH_MSG("Registered %lu on the metaPage of %s track %lu~>%lu.", curPageNum[tid], - streamName.c_str(), tid, mapTid); -#if defined(__CYGWIN__) || defined(_WIN32) - IPC::preservePage(curPage[tid].name); + if (inserted){IPC::preservePage(curPage[idx].name);} #endif - } - // Close our link to the page. This will NOT destroy the shared page, as we've set master to false upon construction - // Note: if there was a registering failure above, this WILL destroy the shared page, to prevent a memory leak - curPage.erase(tid); - curPageNum.erase(tid); + // Close our link to the page. This will NOT destroy the shared page, as we've set master to + // false upon construction Note: if there was a registering failure above, this WILL destroy the + // shared page, to prevent a memory leak + curPage.erase(idx); + curPageNum.erase(idx); } /// Buffers a live packet to a page. @@ -460,66 +303,44 @@ namespace Mist{ /// Initiates/continues negotiation with the buffer as well ///\param packet The packet to buffer void InOutBase::bufferLivePacket(const DTSC::Packet &packet){ - nProxy.bufferLivePacket(packet, myMeta); + char *data; + size_t dataLen; + packet.getString("data", data, dataLen); + bufferLivePacket(packet.getTime(), packet.getInt("offset"), packet.getTrackId(), data, dataLen, + packet.getInt("bpos"), packet.getFlag("keyframe")); + /// \TODO META Build something that should actually be able to deal with "extra" values } - void negotiationProxy::bufferLivePacket(const DTSC::Packet &packet, DTSC::Meta &myMeta){ - myMeta.vod = false; - myMeta.live = true; + void InOutBase::bufferLivePacket(uint64_t packTime, int64_t packOffset, uint32_t packTrack, const char *packData, + size_t packDataSize, uint64_t packBytePos, bool isKeyframe){ + meta.refresh(); + meta.setLive(); + // Store the trackid for easier access - unsigned long tid = packet.getTrackId(); - VERYHIGH_MSG("Buffering %s packet on track %lu: %llums, %db", myMeta.tracks[tid].codec.c_str(), - tid, packet.getTime(), packet.getPayloadLen()); // Do nothing if the trackid is invalid - if (!tid){ - WARN_MSG("Packet without trackid!"); - return; - } - // negotiate track ID if needed - continueNegotiate(tid, myMeta); - // If the track is declined, stop here - if (trackState[tid] == FILL_DEC){ - WARN_MSG("Track %lu declined", tid); - preBuffer[tid].clear(); - return; - } - // Not accepted yet? Buffer. - if (trackState[tid] != FILL_ACC){ - preBuffer[tid].push_back(packet); - }else{ - if (preBuffer[tid].size()){ - while (preBuffer[tid].size()){ - bufferSinglePacket(preBuffer[tid].front(), myMeta); - preBuffer[tid].pop_front(); - } - } - bufferSinglePacket(packet, myMeta); - } - } + if (packTrack == INVALID_TRACK_ID){return;} - void negotiationProxy::bufferSinglePacket(const DTSC::Packet &packet, DTSC::Meta &myMeta){ // Store the trackid for easier access - unsigned long tid = packet.getTrackId(); - // This update needs to happen whether the track is accepted or not. - bool isKeyframe = false; - if (myMeta.tracks[tid].type == "video"){ - isKeyframe = packet.getFlag("keyframe"); - }else{ - if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0){ + Util::RelAccX &tPages = meta.pages(packTrack); + + if (M.getType(packTrack) != "video"){ + isKeyframe = false; + if (!tPages.getEndPos()){ // Assume this is the first packet on the track isKeyframe = true; }else{ - unsigned long lastKey = pagesByTrack[tid].rbegin()->second.lastKeyTime; - if (packet.getTime() - lastKey > AUDIO_KEY_INTERVAL){isKeyframe = true;} + if (packTime - tPages.getInt("lastkeytime", tPages.getEndPos() - 1) >= 5000){ + isKeyframe = true; + } } } // For live streams, ignore packets that make no sense // This also happens in bufferNext, with the same rules - if (myMeta.live){ - if (packet.getTime() < myMeta.tracks[tid].lastms){ - HIGH_MSG("Wrong order on track %lu ignored: %lu < %lu", tid, packet.getTime(), - myMeta.tracks[tid].lastms); + if (M.getLive()){ + if (packTime < M.getLastms(packTrack)){ + HIGH_MSG("Wrong order on track %" PRIu32 " ignored: %" PRIu64 " < %" PRIu64, packTrack, + packTime, M.getLastms(packTrack)); return; } if (packet.getTime() > myMeta.tracks[tid].lastms + 30000 && myMeta.tracks[tid].lastms){ @@ -529,296 +350,68 @@ namespace Mist{ } // Determine if we need to open the next page - int nextPageNum = -1; - if (isKeyframe && trackState[tid] == FILL_ACC){ + uint32_t nextPageNum = INVALID_KEY_NUM; + if (isKeyframe){ + uint64_t endPage = tPages.getEndPos(); + // If there is no page, create it - if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0){ - nextPageNum = 1; - pagesByTrack[tid][nextPageNum].dataSize = DEFAULT_DATA_PAGE_SIZE; // Initialize op 25mb - pagesByTrack[tid][nextPageNum].pageNum = nextPageNum; - pagesByTrack[tid][nextPageNum].curOffset = 0; - pagesByTrack[tid][nextPageNum].firstTime = packet.getTime(); + if (!endPage){ + nextPageNum = 0; + tPages.addRecords(1); + tPages.setInt("firstkey", 0, 0); + tPages.setInt("firsttime", packTime, 0); + tPages.setInt("size", DEFAULT_DATA_PAGE_SIZE, 0); + tPages.setInt("keycount", 0, endPage); + tPages.setInt("avail", 0, 0); + ++endPage; } - // Take the last allocated page - std::map::reverse_iterator tmpIt = pagesByTrack[tid].rbegin(); - unsigned long currentPageNum = tmpIt->first; - if (!pagesByTrack[tid][currentPageNum].curOffset){ - pagesByTrack[tid][currentPageNum].firstTime = packet.getTime(); - } - // Compare on 8 mb boundary - if ((tmpIt->second.curOffset > FLIP_DATA_PAGE_SIZE || - (packet.getTime() - tmpIt->second.firstTime) > FLIP_TARGET_DURATION) && - tmpIt->second.keyNum > 0){ + + uint64_t prevPageTime = tPages.getInt("firsttime", endPage - 1); + // Compare on 8 mb boundary and target duration + if (tPages.getInt("avail", endPage - 1) > FLIP_DATA_PAGE_SIZE || packTime - prevPageTime > FLIP_TARGET_DURATION){ // Create the book keeping data for the new page - nextPageNum = currentPageNum + tmpIt->second.keyNum; - HIGH_MSG("We should go to next page now, transition from %lu to %d", tmpIt->second.pageNum, nextPageNum); - pagesByTrack[tid][nextPageNum].dataSize = DEFAULT_DATA_PAGE_SIZE; - pagesByTrack[tid][nextPageNum].pageNum = nextPageNum; - pagesByTrack[tid][nextPageNum].firstTime = packet.getTime(); + nextPageNum = tPages.getInt("firstkey", endPage - 1) + tPages.getInt("keycount", endPage - 1); + HIGH_MSG("Live page transition from %" PRIu32 ":%zu to %" PRIu32 ":%" PRIu32, packTrack, + tPages.getInt("firstkey", endPage - 1), packTrack, nextPageNum); + tPages.addRecords(1); + tPages.setInt("firstkey", nextPageNum, endPage); + tPages.setInt("firsttime", packTime, endPage); + tPages.setInt("size", DEFAULT_DATA_PAGE_SIZE, endPage); + tPages.setInt("keycount", 0, endPage); + tPages.setInt("avail", 0, endPage); + ++endPage; } - pagesByTrack[tid].rbegin()->second.lastKeyTime = packet.getTime(); - pagesByTrack[tid].rbegin()->second.keyNum++; + tPages.setInt("lastkeytime", packTime, endPage - 1); + tPages.setInt("keycount", tPages.getInt("keycount", endPage - 1) + 1, endPage - 1); } // Set the pageNumber if it has not been set yet - if (nextPageNum == -1){ - if (curPageNum.count(tid)){ - nextPageNum = curPageNum[tid]; + if (nextPageNum == INVALID_KEY_NUM){ + if (curPageNum.count(packTrack)){ + nextPageNum = curPageNum[packTrack]; }else{ - if (pagesByTrack.count(tid)){ - nextPageNum = pagesByTrack[tid].begin()->first; - }else{ - nextPageNum = 1; - } + nextPageNum = 0; } } // If we have no pages by track, we have not received a starting keyframe yet. Drop this packet. - if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0){ - INFO_MSG("Track %lu not starting with a keyframe!", tid); + if (!tPages.getEndPos()){ + INFO_MSG("Track %" PRIu32 " not starting with a keyframe!", packTrack); return; } - // Check if the correct page is opened - if (!curPageNum.count(tid) || nextPageNum != curPageNum[tid]){ - if (curPageNum.count(tid)){ + if (!curPageNum.count(packTrack) || nextPageNum != curPageNum[packTrack]){ + if (curPageNum.count(packTrack)){ // Close the currently opened page when it exists - bufferFinalize(tid, myMeta); + bufferFinalize(packTrack); } // Open the new page - if (!bufferStart(tid, nextPageNum, myMeta)){ + if (!bufferStart(packTrack, nextPageNum)){ // if this fails, return instantly without actually buffering the packet - WARN_MSG("Dropping packet %s:%llu@%llu", streamName.c_str(), tid, packet.getTime()); + WARN_MSG("Dropping packet %s:%" PRIu32 "@%" PRIu64, streamName.c_str(), packTrack, packTime); return; } } // Buffer the packet - bufferNext(packet, myMeta); - } - - void InOutBase::continueNegotiate(unsigned long tid, bool quickNegotiate){ - nProxy.continueNegotiate(tid, myMeta, quickNegotiate); - } - - void InOutBase::continueNegotiate(){nProxy.continueNegotiate(myMeta);} - - negotiationProxy::negotiationProxy(){ - encrypt = false; - negTimer = 0; - } - - void negotiationProxy::continueNegotiate(DTSC::Meta &myMeta){ - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (!trackState.count(it->first) || - (trackState[it->first] != FILL_ACC && trackState[it->first] != FILL_DEC)){ - continueNegotiate(it->first, myMeta); - } - } - } - - void negotiationProxy::continueNegotiate(unsigned long tid, DTSC::Meta &myMeta, bool quickNegotiate){ - if (!tid){return;} - if (userClient.getData()){userClient.keepAlive();} - if (trackMap.count(tid) && !trackState.count(tid)){ - // If the trackmap has been set manually, don't negotiate - HIGH_MSG("TrackMap manual, not negotiating track IDs"); - trackState[tid] = FILL_ACC; - char pageName[NAME_BUFFER_SIZE]; - snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), tid); - metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true); - metaPages[tid].master = false; - return; - } - // dont try to re-negoiate existing tracks, if this is what you want, remove the tid from the trackState before calling this function - if (trackState.count(tid) && (trackState[tid] == FILL_DEC || trackState[tid] == FILL_ACC)){ - return; - } - if (!trackOffset.count(tid)){ - if (trackOffset.size() > SIMUL_TRACKS){ - WARN_MSG("Trackoffset too high"); - return; - } - // Find a free offset for the new track - for (int i = 0; i < SIMUL_TRACKS; i++){ - bool isFree = true; - for (std::map::iterator it = trackOffset.begin(); - it != trackOffset.end(); it++){ - if (it->second == i){ - isFree = false; - break; - } - } - if (isFree){ - trackOffset[tid] = i; - break; - } - } - } - // Now we either returned or the track has an offset for the user page. - // Get the data from the userPage - if (!userClient.getData()){ - char userPageName[100]; - sprintf(userPageName, SHM_USERS, streamName.c_str()); - userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); - } - char *tmp = userClient.getData(); - if (!tmp){ - DEBUG_MSG(DLVL_FAIL, "Failed to negotiate for incoming track %lu, there does not seem to be a connection with the buffer", - tid); - return; - } -#if defined(__CYGWIN__) || defined(_WIN32) - static std::map preservedTempMetas; -#endif - unsigned long offset = 6 * trackOffset[tid]; - // If we have a new track to negotiate - if (!trackState.count(tid)){ - memset(tmp + offset, 0, 4); - if (quickNegotiate){ - unsigned long finalTid = tid; - unsigned short firstPage = 1; - MEDIUM_MSG("Buffer has indicated that incoming track %lu should start writing on track " - "%lu, page %lu", - tid, finalTid, firstPage); - trackMap[tid] = finalTid; - if (myMeta.tracks.count(finalTid) && myMeta.tracks[finalTid].lastms){ - myMeta.tracks[finalTid].lastms = 0; - } - trackState[tid] = FILL_ACC; - - char pageName[NAME_BUFFER_SIZE]; - snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_META, streamName.c_str(), finalTid); - metaPages[tid].init(pageName, 8 * 1024 * 1024, true); - metaPages[tid].master = false; - DTSC::Meta tmpMeta; - tmpMeta.tracks[finalTid] = myMeta.tracks[tid]; - tmpMeta.tracks[finalTid].trackID = finalTid; - JSON::Value tmpVal = tmpMeta.toJSON(); - std::string tmpStr = tmpVal.toNetPacked(); - memcpy(metaPages[tid].mapped, tmpStr.data(), tmpStr.size()); - -#if defined(__CYGWIN__) || defined(_WIN32) - IPC::preservePage(pageName); - preservedTempMetas[tid] = pageName; -#endif - - snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), finalTid); - metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true); - metaPages[tid].master = false; - Bit::htobl(tmp + offset, finalTid | 0xC0000000); - Bit::htobs(tmp + offset + 4, firstPage); - }else{ - INFO_MSG("Starting negotiation for incoming track %lu, at offset %lu", tid, trackOffset[tid]); - memset(tmp + offset, 0, 4); - tmp[offset] = 0x80; - tmp[offset + 4] = ((tid >> 8) & 0xFF); - tmp[offset + 5] = (tid & 0xFF); - trackState[tid] = FILL_NEW; - } - return; - } - switch (trackState[tid]){ - case FILL_NEW:{ - unsigned long newTid = ((long)(tmp[offset]) << 24) | ((long)(tmp[offset + 1]) << 16) | - ((long)(tmp[offset + 2]) << 8) | tmp[offset + 3]; - INSANE_MSG("NewTid: %0.8lX", newTid); - if (newTid == 0x80000000u){ - INSANE_MSG("Breaking because not set yet"); - negTimer++; - break; - } - HIGH_MSG("Track %lu temporarily mapped to %lu", tid, newTid); - negTimer = 0; - - char pageName[NAME_BUFFER_SIZE]; - snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_META, streamName.c_str(), newTid); - metaPages[tid].init(pageName, 8 * 1024 * 1024, true); - metaPages[tid].master = false; - DTSC::Meta tmpMeta; - tmpMeta.tracks[newTid] = myMeta.tracks[tid]; - tmpMeta.tracks[newTid].trackID = newTid; - tmpMeta.tracks[newTid].fragments.clear(); - tmpMeta.tracks[newTid].keySizes.clear(); - tmpMeta.tracks[newTid].keys.clear(); - tmpMeta.tracks[newTid].parts.clear(); - JSON::Value tmpVal = tmpMeta.toJSON(); - if (!myMeta.tracks[tid].type.size() || !myMeta.tracks[tid].codec.size()){ - FAIL_MSG("Negotiating a track without metadata. This is a serious issue, please report " - "this to the developers."); - BACKTRACE; - } - std::string tmpStr = tmpVal.toNetPacked(); - memcpy(metaPages[tid].mapped, tmpStr.data(), tmpStr.size()); - HIGH_MSG("Temporary metadata written for incoming track %lu, handling as track %lu", tid, newTid); -// Not actually removing the page, because we set master to false -#if defined(__CYGWIN__) || defined(_WIN32) - IPC::preservePage(pageName); - preservedTempMetas[tid] = pageName; -#endif - metaPages.erase(tid); - trackState[tid] = FILL_NEG; - trackMap[tid] = newTid; - break; - } - case FILL_NEG:{ - unsigned long finalTid = ((long)(tmp[offset]) << 24) | ((long)(tmp[offset + 1]) << 16) | - ((long)(tmp[offset + 2]) << 8) | tmp[offset + 3]; - unsigned long firstPage = firstPage = ((long)(tmp[offset + 4]) << 8) | tmp[offset + 5]; - if (firstPage == 0xFFFF){ - HIGH_MSG("Negotiating, but firstPage not yet set, waiting for buffer"); - negTimer++; - break; - } -#if defined(__CYGWIN__) || defined(_WIN32) - IPC::releasePage(preservedTempMetas[tid]); - preservedTempMetas.erase(tid); -#endif - if (finalTid == 0xFFFFFFFF){ - WARN_MSG("Buffer has declined incoming track %lu", tid); - memset(tmp + offset, 0, 6); - trackState[tid] = FILL_DEC; - trackMap.erase(tid); - break; - } - negTimer = 0; - // Reinitialize so we can be sure we got the right values here - finalTid = ((long)(tmp[offset]) << 24) | ((long)(tmp[offset + 1]) << 16) | - ((long)(tmp[offset + 2]) << 8) | tmp[offset + 3]; - firstPage = ((long)(tmp[offset + 4]) << 8) | tmp[offset + 5]; - if (finalTid == 0xFFFFFFFF){ - WARN_MSG("Buffer has declined incoming track %lu", tid); - memset(tmp + offset, 0, 6); - trackState[tid] = FILL_DEC; - trackMap.erase(tid); - break; - } - - firstPage++; - MEDIUM_MSG("Buffer says %s:%lu should start writing on track %lu, page %lu", - streamName.c_str(), tid, finalTid, firstPage); - trackMap[tid] = finalTid; - if (myMeta.tracks.count(finalTid) && myMeta.tracks[finalTid].lastms){ - myMeta.tracks[finalTid].lastms = 0; - } - trackState[tid] = FILL_ACC; - char pageName[NAME_BUFFER_SIZE]; - snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), finalTid); - metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, false, false); - if (!metaPages[tid].mapped){metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true);} - metaPages[tid].master = false; - - if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0){ - pagesByTrack[tid][firstPage].dataSize = DEFAULT_DATA_PAGE_SIZE; // Initialize op 25mb - pagesByTrack[tid][firstPage].pageNum = firstPage; - pagesByTrack[tid][firstPage].firstTime = 0; - pagesByTrack[tid][firstPage].curOffset = 0; - } - break; - } - default: - // We can't get here because we catch this case in the beginning of the function, - // this case surpresses a compiler warning - break; - } + bufferNext(packTime, packOffset, packTrack, packData, packDataSize, packBytePos, isKeyframe); } }// namespace Mist diff --git a/src/io.h b/src/io.h index 381f94c5..d06805eb 100644 --- a/src/io.h +++ b/src/io.h @@ -2,95 +2,46 @@ #include #include +#include #include #include #include #include //LTS namespace Mist{ - enum negotiationState{ - FILL_NEW, ///< New track, just sent negotiation request - FILL_NEG, ///< Negotiating this track, written metadata - FILL_DEC, ///< Declined Track - FILL_ACC ///< Accepted Track - }; - - struct DTSCPageData{ - DTSCPageData() - : pageNum(0), keyNum(0), partNum(0), dataSize(0), curOffset(0), firstTime(0), lastKeyTime(-5000){} - unsigned long pageNum; /// The current page number - unsigned long keyNum; ///< The number of keyframes in this page. - unsigned long partNum; ///< The number of parts in this page. - unsigned long long int dataSize; ///< The full size this page should be. - unsigned long long int curOffset; ///< The current write offset in the page. - unsigned long long int firstTime; ///< The first timestamp of the page. - unsigned long lastKeyTime; ///< The last key time encountered on this track. - }; - - class negotiationProxy{ - public: - negotiationProxy(); - void clear(); - void initiateEncryption(); // LTS - bool bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta &myMeta); - void bufferNext(const DTSC::Packet &pack, DTSC::Meta &myMeta); - void bufferFinalize(unsigned long tid, DTSC::Meta &myMeta); - void bufferLivePacket(const DTSC::Packet &packet, DTSC::Meta &myMeta); - void bufferSinglePacket(const DTSC::Packet &packet, DTSC::Meta &myMeta); - bool isBuffered(unsigned long tid, unsigned long keyNum); - unsigned long bufferedOnPage(unsigned long tid, unsigned long keyNum); - - std::map > pagesByTrack; ///< Holds the data for all pages of a track. Based on unmapped tids - - // Negotiation stuff (from unmapped tid's) - std::map trackOffset; ///< Offset of data on user page - std::map trackState; ///< State of the negotiation for tracks - std::map trackMap; ///< Determines which input track maps onto which "final" track - std::map metaPages; ///< For each track, holds the page that describes which dataPages are mapped - std::map curPageNum; ///< For each track, holds the number page that is currently being written. - std::map curPage; ///< For each track, holds the page that is currently being written. - std::map > preBuffer; ///< For each track, holds to-be-buffered packets. - - IPC::sharedClient userClient; ///< Shared memory used for connection to Mixer process. - - std::string streamName; ///< Name of the stream to connect to - - bool encrypt; - Encryption::verimatrixData vmData; - std::map iVecs; - IPC::sharedPage encryptionPage; - - void continueNegotiate(unsigned long tid, DTSC::Meta &myMeta, bool quickNegotiate = false); - void continueNegotiate(DTSC::Meta &myMeta); - - uint32_t negTimer; ///< How long we've been negotiating, in packets. - }; - ///\brief Class containing all basic input and output functions. class InOutBase{ public: - void initiateMeta(); - bool bufferStart(unsigned long tid, unsigned long pageNumber); - void bufferNext(const DTSC::Packet &pack); - void bufferFinalize(unsigned long tid); - void bufferRemove(unsigned long tid, unsigned long pageNumber); - virtual void bufferLivePacket(const DTSC::Packet &packet); - long unsigned int getMainSelectedTrack(); + InOutBase(); + + bool isBuffered(size_t idx, uint32_t keyNum); + size_t bufferedOnPage(size_t idx, size_t keyNum); + + size_t getMainSelectedTrack(); + + bool bufferStart(size_t idx, size_t pageNumber); + void bufferFinalize(size_t idx); + void bufferRemove(size_t idx, size_t pageNumber); + void bufferLivePacket(const DTSC::Packet &packet); + + void bufferNext(uint64_t packTime, int64_t packOffset, uint32_t packTrack, const char *packData, + size_t packDataSize, uint64_t packBytePos, bool isKeyframe); + void bufferLivePacket(uint64_t packTime, int64_t packOffset, uint32_t packTrack, const char *packData, + size_t packDataSize, uint64_t packBytePos, bool isKeyframe); protected: - void continueNegotiate(unsigned long tid, bool quickNegotiate = false); - void continueNegotiate(); - bool standAlone; - negotiationProxy nProxy; - DTSC::Packet thisPacket; // The current packet that is being parsed std::string streamName; - std::set selectedTracks; ///< Stores the track id's that are either selected for playback or input + DTSC::Meta meta; + const DTSC::Meta &M; - DTSC::Meta myMeta; ///< Stores either the input or output metadata + std::map userSelect; + + std::map curPageNum; ///< For each track, holds the number page that is currently being written. + std::map curPage; ///< For each track, holds the page that is currently being written. }; }// namespace Mist diff --git a/src/output/mist_out.cpp b/src/output/mist_out.cpp index a24fcc61..78f7927a 100644 --- a/src/output/mist_out.cpp +++ b/src/output/mist_out.cpp @@ -12,6 +12,7 @@ int spawnForked(Socket::Connection &S){ void handleUSR1(int signum, siginfo_t *sigInfo, void *ignore){ HIGH_MSG("USR1 received - triggering rolling restart"); Util::Config::is_restarting = true; + Util::Config::logExitReason("setting is_active to false because of received USR1"); Util::Config::is_active = false; } diff --git a/src/output/noffmpeg.jpg b/src/output/noffmpeg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f4844eea08829ed55fe72266d050924ec2472fd2 GIT binary patch literal 13156 zcmeHs2~<-uosHKWv5v&Rbii*Q`ZUDuhee}KcuYaxYd)sjDx%=+D&z{ac z`yO9id({P~ZqBaG06`G&8T;oO7l`b?M3%X^&`U{@B&Yf|=NYVN5w3vojKW zK=AvLgI8ie$=5p!Alb3#@jy8KH30y*?5I_RQP~F3kDUy-P6o?EYg$#gE_MLeMnjqp z0l&W`c%v^+NQmOC7XYJ8PI$a}Wj%KRu>KY}o&^XQ3%bS9fL7WebQzFmssewa6YW{H z(T~lLY#FrVLvn%kGrm@q7a+h0ekKBPS^IJMwo#XIF`!Y2nHC8^uVQ5x0Q^cwro7d& zt4}H>Q>LpRNz4`e+cM9583<;2qQxSD6+c+b`3Dsu_bOJ@e%@6ajw6* zuD@Cw`t@{+z_?c@)iBylY8Bv9GtM6|n?(NQ9VS1|_YH;nNUyo<(tZ(WS%9p9zMKx& zR+vwjOC}JSW-Zgzi4+BG#O?drfF#YF=FERv^nV%t-+Dl0f27sFHv`h5whEgrE3%hf z9$-vy`6jAe9RPx2vs#ArI-lW-VuE#MAPB`pn&8^DxnyejKNGn?<{NvM<1^8%DE08NSpnr#vOGsL<5azX3e$4L!vaF9irYi&z z+d&p|lPhVpgm|H=5-*kR9SQMFz=xP=i{TTBm%tidkP4@E4pz!}201m<^ortvcJ0U0 z50kdIzYQ8vY5)VIYK`_Rhr8}f`DDSruZ-THz-Rb74CN{=z>8r>E=G_MOwX)|Dm~u- z-{9nqW;lihD~dJ0fF9B}m+879^E+*Na)3f$E8OxD@hg&LZ00P110WM9-1plw1^k!n zf0qXg3N~WEuS6ZsgGo5|5e@F(Q@~%|8TNZ01lTMQM8Lz9Q8PsX>r`;RHor ziNgFCTtnDGI~jYx&T&$d!hF3n*nWezq(S5X*vbJ=dqQ)f#~YU!ncvb$2ut_Nj>$IzMDLkO0t7DSQ7b?!0PTX~cbotc z9SW>gQ;yW)J>(R4BOiR?g$D9rO}i&C5{Z2TU_A%#neo|ScPD~sC-%3j?)RMo$pbsdA`q}|v zN1A-6L?H=^f}b#Ddbcm++`B|36hH=8wBHHbgnRE?%8S2`N)`NG_ z)6yT(%d&zR1e2Llhn%*WP&*+8k>DGUPp<%PEUn5QE#5z$z5r9v{ek?yc1~6l$4=kP z2D{U7D^XqpYB zP|V=CAwk{Dr+K!xWTf)q+2!&-4&CjDS*~biRR*8NZ+}FD9sZ-r&Z|G5FiGN|C5pCo zAZ{6$wPbF~&`8^yrhG-#msiX$He9l*b+f9aGLl$J$@=cfDESf*i^dTtUO$tgh#R9B z4w}Ausm>XCMCBsN$|9vpeKrJl+OaP_AH&Z=l;1uf0bpEz0)p5hq};!tK;KYM-<8od z;R6|alo&MbGK7Sm#^4}z8Um=wjJYh1FBYm>mOAM&d)>hT6Kw+%%CfXWOn`CA?e#e(y>878}KjfCa zXq^egXLTvJxS*7~I^A%EEw|9az=T_Td9kdmrfR6qZgAN&c!o;IBDv}@CkM_(k^_gs zPoYqvMUv_YTbk_i@O7N9=sbtM<#j^DF+pW8kDanpUa_i4gO8ayVO=&+UZV0)8~G^i z;_kD-yhiUw>gM({E35n*lWk^fDf=~tci7xKJclO^LA!-bgdsn_(>6Wk-rfVM$u^3; z3xvPBrJF+a5)_`KjuX$jBc+~Ov9QCoNn7@Nub>MyKOffMS9{uYS!Z0=xeMe8++E{y zRsMXWo7aR?H%iKG{0=3V%T~Tc9>sHfhK(-AwcXldzI|Vlw#=Zpxm`{^jk$8*kQP6A z-tUdLCJPVL4`*rn4GQBrdlIX7hZolOBr~oA+IoE9vHW~0bSpcxAhK}E%dM-kqQvoT zX$2jehv`e&$8GxJ!jMgm;7S5=!9vZr#D4XH)VyWkoII?DLL(smlT9NSlrZ+ z^r9hd*zW9I=a)U5yIgKkLhRRAi{sC6H_u*0a=^k_`Gd!vBr;!tc&jpvSKzF!DLZSq z;R;tSjA5~846Q8^52ah@ikF-yTUwz;Q6!x!V6OX0_tAO*Ct2XNNA`hJLqcNdZO4?# zS%wYr$%cD6XQ(DKchF}Co0;<(X0Usfbi(|J*XiRm{hA=3!*i^WPdTklx9Ju(^2{yo zHwZ7-;53KnhE2$062z1ibR5=pG9*0u0zQ<}(oJ{J(@mEvK08@=N85+yW0T!< zS!m6fH-x!8Pi+-u*Mzv z1UhXAV}MZ8&~(S#^1>`*ZO+A=NZ0ntVJn-SDBjV~A)D@kHEy}r7AcMD+?|_{*yX(; z2lsaV@KbJ~Yv1_r zdvI8&2F0oCz|0?)*2H@v@E%;)>AhWBK5rH1`JU9`80p#z`1^|sS z?J3?O6jA{^ss(_7C@=(oDuyq~)H*Uw6OCV4XQBD{(>ox7djcRL7?TZkb|C7*UMC(f z$YcQVGE6{Zu%t5H;ewA7dQyl2{2dDw0MT9`fv5QbKp+8>0#>5Q z05=(4Ve3a0%SZ?BT_z2LFPI`Y1vC%?s0_fOa8l}0mKeXk50eH8;9U5m&_F$ep#nb< zqR9S~_jc{q0!Yw!A_IU_0YJ|~#Td{()M)&*xE6>B3~HUiYudjaNDZ7MiG>3O?&xWN zXbKJ9qU@E%V+!(5h787=7gW$p{j;2TL-nB%VY>Exi21*`|9>8ME8qT(8lveFN6(Zl z1u|O>AnuH_u$WH|@?k|p0Tp2Y1-K_rVHKPnFkw{$0x}zJ9C)3XHozrM0wNS&aJtyE zxnOyThg$|-E@0iBs65l-&KCd<)DVN!W}2&YTrR9>G(2$d2LaZ6>HS|{#tj4vf&x(t zx#3PRZ9t@xI7&Gn?|-H-%vf|n`8?-v0CZdZ6Jv)~^I{sF+ow{Q6W($daEibsi4b;fCM$umW8 zQ>G9>1Fjev}x5Mt#38I-GMuBzS0B3sNK&fO7?39SFH)b;S+Q734tU4lJ(w6(WyeFHMakY^Bnxdhx#v1e8wrvr~pBUBmxmK!R8DCqC6K+89ZHwm3~pI z@^;StyjslnR_I{a>!Tg&hzhno#d5df8^~%SD{9v08(w z!kFi33WUdbYi0*J<~=^}WN7|JR>t}iNu7>D#w$QHL~G|Hrq_R;3KXqKUBJ{JBC?sp zWPnZy_B&v#7(7|LW}luvmQmyVo=%f z60v=t!Qld0ptp$yCQkWS1@j2ld^;lPpZj}#RR#EM!I@kw>Tq+LlP;J) zNyyV|xHv7~yXL{`nlOG>OD8ALBDNNc_Ms{UbHV)uO8fYHv(6pACbeYUIqf#~b>L5#r?Wb% zW8!c0R9_ES$>$r{5t(|sj?ddy>^Rh`n)hT_PVzh=w(qM&bz%0i{Oo3@u#U`V%JAIN z(?T^|nw2fPw5sY*@ZzAxUJw4Sg}W|StZj4K`cTPyPGs`%#u}&AmW;l`)(2=`{_st& zf8qC*hG-f`b#C3u?|b-0RrfERxqIE>PN8jtEM2XpDWc*AwAnpud3NU99ZO0`PX0^={tA(kG~RNDGp?=i?7$k?~wxZ;G-t3%=vA>>7$>t z?_O;3bF)q5HxCxJ7CoX{&kM-+ap$}=X!?iE=0_|?o}8pEjOB9AD9*6$Tbs^v-$sW0 z^M~&fp7wkCI5aeb@NNWImsC|9&_S{F55E+J$^=kr{8yg$*oz)4;e1~>-jKegbGpm%0Q_x4QWyRS?QsInGr>y zvHU*0&4stRFZ(3%JvP+cjdD)4h=}SWPJk5A#u$?SKBNWs6cg6TxQ-F1ow7r z{8IrL&{`vFT7Jd)+~ED7T2(P+NA2M{fRwNYT`fAA7tQ|6K@_V=b|`j`iKYhHF|DXE z4ibj~hJ>vsD@DzLsaxMnsy7bzER*Gv3EN!Ym?w8dwKS<@lPgx{mY%8~nu)+ z(n1&LF05Tt9+$~{kpm+(J7AvC#U1%sMwH|i zDx5DfswN27w}`r~s3Ue2L^q~keAyM%D*t5A#Aj#x@-xiEr?&}F(T==tDBG3{RcWOb zufp`pt^fiu=D@j?v*pWZlr^Ga9B3TQ*5<~R3BDBQ?;av*&IojlR)&Vo2zkEJ&6WIX zL#};uz3;x4Oz+O0t!MA<7Phv0#~Yn|tTgarA{)~P8J@jnPhGCHw2d-0@^o2?cz zWA9VJ$ED`MJ3Oq1#y(GKDLW8SPFaIxM%F!k1uCNzN~}!w53HeQSHJ@4SstdMb=`M= zH^Rw}rsT-y#+|~f36uDL_7nLTv z<(X~e*o)f6-hkXw?8YFj+m|Yc8%9<+h4vb4QuX{zp>x;Et{y&%imZyrju544B=4d+ z4(qY)G{)H+8vYpYsD2{421(?@oKjQ`(duy)WBIE$vLl8ZpWL<19R0er>E;6^>v^Fy zyZTS((iS&uR622K$1rbnEGDTX$71b{pgT=n9=$=mZ!=@%nas$r6;va#T5r$5sH|3| zjnA2pU)$mlr}E71tPxkPyMQa}Xg#|UR)xApqeVA%a>b>VE$SyW zH+*9)9Eh1?E*J0FZ-&7WO07N-HB1zqIx#DJyFmE|aBSysaENM|n{xf}!p5d>?EFb2 z{Mvw)P8ZP1$9})BrC_mAz5~-fF9xjsRO>7B65(CFKJ;7&erS&@74|VS6G>k91}=)) zD;sjbV0^cBqqn=dy9aMix-b}%+xL%BSbNuH`1w8kt^ZWpZYNl_14hg{ViMv{y0glj zKNz`w=cjyFo6DckH^I{QnbAzIueR?9;@@oN(O=}ualB8FjA3dsXFGci?Evb}zkQpt zzoE4)VtvoR!ymay=BDRZb!UfvytMh~D^N_gUi^D@%1`I-yVz8IZE#k8#)ScoZzEsk zcz2E-3SROG%sIvA?mlrq!OVVP&4Q|d{`2#xNz1C4W=`Y{b9Nx46c@n-)x3lyyZ3Ks zduW!-vGzEI1LR*CXJwowN!~|BNgj%zluV3G4>C+%&qA{uJ!9kU2C=> zJ3c4Gc!=VwRV^@#-NL4JNg^V-61Vh>g-$n>!z=bTadk^6m91 zv(M#i8k_K5T>(qG1zkfg6W(Q{@)jLyichUEl{D6Our=`5jZfgb@v+FT%u0bR2{-x< z3puh>>52fe$|A!Wdokk4Sga;nQCB84#w|4sy8`Yc&NuP5%`EX)`0=EhXlaTq9rYD} z!1-2pB$GMdz*Lvxe)p-WKmh!#K22=D@8~@`!flMGENvWCn^U}FkJ^2@PTKMAyfBWSy> zw12?Fvq)5TbaF}Lode6AYm;KMWELoFnR#t)S%7aNe4Gi^j%lpx-Lm_a5zY7Ioq zDLW-|C!FGV&j`t_{!H2pt}rt~4+^M;dWQp8enSb2qh9Q)HH5oCkImI`J}N9~mQ%bG~9x@xttBk{sTcEWqPBII3= zlhpxof$T?=q=t2zHPLvJuUV@T3&;nixaM;GBC6vR#Ee1S8Oa@tnZH7#`1*K?oa?CD z#zNAS4SBJ5eaWUpaETbNlIsx5tt1?-*uttgw)wX6)n?&+X}|d!TJ5ZSN^7kiy#i@D z0SJ`6q^_gRFnNM@?)8z#S^|O`H4JC(z|tF4OJnbDV))r*coxYZc0_b=nOkm) lRX5BchJ3Y8wZbAmYqnID7+CG~@+?v<6;O?zu6Xs!{{Z7WYV-gA literal 0 HcmV?d00001 diff --git a/src/output/noh264.jpg b/src/output/noh264.jpg new file mode 100644 index 0000000000000000000000000000000000000000..55b3eafef02b1201809f33333dc068ea6f2e104f GIT binary patch literal 11143 zcmeHLdpwj|_utRVU@({oQAP$QcN(Hxl5v-$aVh0Eg(5j!i0VlF8kdkFxh0JnMd+fG zQ`D*B8kNeeI!PsDR3}bXo$BP>`+0_N&hLEQ^S-~&=l4&$r)TzBd+oK?`mVLtwx8i& zhPwdW)oGp+z%UFfh5x|teP9oU|Jw$YVE?U-;Tpi8lH)0-$(RNpF)%U%8?FON$W#oB zZ6u5g2PXfC4=~bj2cVHLK%$fB0APZ#v%dDfBJdS~{}6#-#q{iY-hVK8%8r$Voq($! zT*?8GE(OUh{Ub`6=FL;s8*NIEG_xE}$C;dwG58yBq9$E8ZVac^EB%hY7YjjLDTjugs1pce+!kkC zfU02Y17Os!{Uv~OECe#Kq}li&@RQx$jQ21L9sujo^9_~7>T_|-QV^K!uKki2HHsr!3k3LI$nojOPH<_AJJ>%;y`Pax)vD4{1ffs7XqfeJ~-^N>B1UjaDBgB{2YSgN}jD3CX5hXiL!&IGH^ zdj7pa8jI|TuR>|=1B#YQ;96S#poD;B0{D%c!0#Tkuu4;C#Lq2RAD42UfbL1q9; zr>yjSVO+pIup`T_!3iY-Y{@*VFa&}Bw=NU108CNgAvZMyW=0_-p7Cs80Duzt2%hW+ z)ymUAE0m*X$|WYitkTs;ANOd6iIPyHavlrw3eE}L;M)WQv<3dsC~c->T9g71DZ~>` z!nz7`>Iak?E~KN$#3Tv!h&||ctiKimKt=`OzaofK1-|8sWYIh1ZE!UYYlWQ&7McZ4 zRv-JU$Q|8uNIkaGxCAN;{1FBe!8_Xk57e$2KrXU^j#C;z=UKRrTRTrLM1W=h5CN?x zdN?0jgaZUQu-u>!%=X-r4R;5EU0f9Rh;=|O@-!fA0^t4)C^^AM%Mo>u502&HRCVQk zqb?K$Z0%{p&{_w;F*Fwd>a{fSE(qFw9;rwG=?Umba3Bvg$j6fn=56nfIT*~_Vp8={ z7-87R8eKqIWF1UkSd=2*ejLi_@3XW>O5RB=o2A@suY8Af@Woo$;?4QW#>u{sVb7*Nm=i923pk&eMUgiUKR z5YGl$TLPr%Wup%O9{P}+iUeRnjvz^9yBCT9SVmR{!FO3aB@##1<$ZHr(+3K$eF(WbwY07kN^ku2ZVyIAR0>F)8$mH^~D$7zo_a1rY#G zKVm?0HW<52kX6dWE|771&}wqDiiol@4|5uj@xL?nf#z!b#>39vMB9OV6^P{$DghwP z48z8iYw@XkePbORvfJWAJUOu=(WYl|(9bo^j;o>`n%LL)Hk7#W@D z^6Gi>D@L@ziJ21uoT>r2tHV58iIHCZG|k|LvhD_V{hMNrimX$X;mabs ze|lOU)t3CaNKpvce4jk?Ke5eEch~NcHJI7g>2rX*4hbCy2t+@8<9tOSEyOXZyI~j8 zxKSnGy)0*_r9N>ob4hr>c-0099*#;k9N~My6L~m!hT2_pwP%boAA-s7X9;lQp<_1K z}l%spg5E2{Q)#~ylpn^I=x0YfEs5oC0jk7UBeJxGSk4G8W69!Br$mq zs#Rc&|6Bk-6|R$nbc!8967-1@T_VWDZAg^0P(mGo3(*A#U?Rv6TsDitHKs#^)on)* zwo(7AEL7`Wum1k~0&Y*LdC%SH&dl(JcS@>KM*gA3%=g zdwvOTyp8}<@+J%dbd8PWz1 z&%?0|YArEK>!h;FEU*(jgPGFabB=B`-@PzIrQ!O* z3swDmC+%{Xo}@=gd?)805DsIX_+IlBN~+1~vAbfUZ7+m`ca|)bcGsNQAxqzrv({SB zsdZb1RGW69TgR$?LaIz&u_m{Dkyr!6qs}v$(-B#Gu7L|Xcf(stEMz=v>%5~m?cuE_ zENcfeX9}V@ts7%jH-<2sEBTJA%B3G1>*=$KEhPEXc3Jo+RukimW#zQue_pIZ1-;lsTRuBZ8&d*&&&eU(Fkjneb#WR4_N3>=Co z3W0nf)2VtWGrT#!aj`pV9p5p2Q0)hys$N<#-;Mx`kyICGti0Pv6YmIQ9D2pC4kbYs zWm(0$V+4F&$K2mivlOfA6!&JN+9YW1Va>XAQRuqM!92Fc6m-sg>1xR7mUgL-X&b0^ z^lVXo#e>-^_v(qFtKpL*P@bp%^qNj8wO+}i_3$TyzQdo)yAFSv=lIFQ1|~HA;S7-B z1UixOU*BI5_=>=Pg#dZke=?oy+oZk|-dSonqS+XYEg-tKG!YB`q1#i&km58}9C6#@ ze&G@f77t!SI6%}ISx~vCkICF*#eFZ@@uEu`8p7{pVNi|-NFq^XfKdUDAh4UPSY6R9 z0K`NR@P`Bi;E_oKcJR%?`8@m=87`yCN|-Y`3knhfxRd~&2fG+t8u6)63EpJBXD7(% zP=G5zqBr+T7dgmt!D+%!4q)MwGCP(gfR{L6$HQs#8t))QBq^E)4F;6i=CRf%I7k)% z0scm>2@cW-R3R9!!-CdIn8!-8I7kwM9MLGEa3121)dpRoA|EpgVyWd0#3%+R1m*-e z!9juvNMI5GraCp$NP&n0XNlvOkI=O=HvWhrNU$eCG5;EBSOLU_O*jo_jWcOvJ`q6? zr40Dp)UJFy5Of|sk%PSZ;vjG%mSfk9)uvEG{U!$L5P2a9yZ975sR-SF;b3bzRr4he z0ZKF$LXSrgj5BF`1fgK&S*rRS9_+w*5LMWy@Bl-wz$QR1yiJH87Dv^*!y^(50>Q&& z;F|&ff=>t7)7Us~TmpEu$=BuFc$taPY&w2k;q#Q;bn0>*>8 zofsc+knBrU-*W}=C_MN=jm{z*?nzcC=!{ffzbSOo07zeb)n<@G5G*# z0sK#_#DTpDO(ukuV52Nz;@*r3uuzXip;r+~2|msQzz3BMg7c5wbf(}R$$u68iojO{ z{%;YmffsQm1HXfhUc+I9pfmgdOQ3Fy-@(y`0{Z*FMEVZas*gK3{qti>W12I&^7~&E ziOFTDx=SgJwSU#)-$uVn z(1$_u$MhASX8>J8`M*mv#JU)x7$ zTE;L)(>(9%m1d;F6z;Py9a28|wxw~i{tb`U9^fby%DZGtjQ_2v3#Qqs|Sp~eJm2xFU;OfTHWw3>Tc_STQ~ zXEQ=G@)YuyvtSwuwn_Jx~8VHn>(J()>e$B&{=kbcMJkw-_Ukm-h;3Q>* z%U0XnPb@0k{#+(iPS1b{OLo8#|_;^d5%!sKX>UWz|(6x)H%yC zR)o5q#C>dEij-ZBx7?Z{%KEqQ^M?+muJ&6=YPldV;g_9%NS>i~-F=KVz1ikf{=(Au zx2mt(&%TJSz8NUx6T1J3w9_-t1YM7}z4dC`0(NSuKgZ~VTsBy2!eqXwnUvtU!%EkO zX()XO+=BK&7e9AId`xKIit5n%=)2zXr;x9HCls5Eu=PimU1ct5&5{! z4%3#uD6*+ovr+8k0_!XK+cxyh$=nigcy03CLm!Oq(|I+itKB)d5|(DMtAqHY<>Jy~ z?SFm%CgW?BIKH~hc8?qSyLXld)=u@g|NUcn)L`sb%P;iZQa-t2o4uU9q5h9^w#s;pd_H;k!d(<&?LrFUle1~5iB=IXES9k9 zWj)Lc+;-{g^7UJ_e?Di0;2H+U*O#7YKXX%A_1kwaxBYh3O{xdaQdAjXZ@OT(9K;~>btLcjh5sD(tUooNzb#UUA5fMuzpEyC zaLvWB*(Y@II+L|QtLVLSvw%66Hw+f*FZ+3m?5*V=YW#ZZx>5)4FlIaq8tYdC1Mc3W zXKzZXwBPXz4@D@Zh$YC&T!4Xfm=@iK<2tf+rZZbB8pV!m)X`nEkHY6VmdhMuE@p-p zS~PnOd~CmX`K{65rTG^J|Asj4XOit%_+Y4H^R{Q-J=(Hw^FxAo<4EkZa8^-}YY$uk zYzM;7MbEl2j<3Jum?BnW^J_gJvHcjTY2l_uZ^#s8>y6(wtF4{hH0wjZ6?>n>F!01{ zDf=w~L9Cw4=SCz}e3<%e!)d!?jc^YgtD=Fxldk6LT5lDi@J zxlM29CHuhqGVU+MYrJRasVK@MC)EE1D*`P!!~byB1V^Q`z$ThxQ~tJ^KbXVZM31oOR?78?WcnQ6+L{ znY6W!5W1~0H)r{hM;32Vev8j9CN$kjLnqsUh3_8uHCoh4EFK2F<$H~38M%dgxVsMb z;YX4gij};bCOr7UB4Xc4Shn5S{ITY7%IDWs&ZYEhy^dWusJ^)s5V_ipeL_cqH}i2^ zrB(ZEJ(Vn(YY9*6H{@4xJ5NoI(!aXQ^_5ThPg%Vq1UKc3r_1qxj3e8BDz>P{p0s!3 zXT%DW(0H+~g=gaOmxfWFt_@DBDC~d4n!hpheKb6zju8fu@G`vsmgz>g@~QXPKYb3W zzM`%$?GL!Pmhe)P)WUV;MZ+Mc;Mx5_p9p+2c%s(%nN%*W0b#i|#~TTE2E|(=5xm+Z9rTtw-Qr8SR*$(`QEV zlb3#%_4n0pcrS=GK}Y)+M(J|J>+r #include +#include #include #include #include @@ -27,11 +28,9 @@ namespace Mist{ JSON::Value Output::capa = JSON::Value(); Util::Config *Output::config = NULL; - int getDTSCLen(char *mapped, long long int offset){return Bit::btohl(mapped + offset + 4);} + uint32_t getDTSCLen(char *mapped, uint64_t offset){return Bit::btohl(mapped + offset + 4);} - unsigned long long getDTSCTime(char *mapped, long long int offset){ - return Bit::btohll(mapped + offset + 12); - } + uint64_t getDTSCTime(char *mapped, uint64_t offset){return Bit::btohll(mapped + offset + 12);} void Output::init(Util::Config *cfg){ capa["optional"]["debug"]["name"] = "debug"; @@ -47,17 +46,6 @@ namespace Mist{ cfg->addOption("noinput", option); } - void Output::bufferLivePacket(const DTSC::Packet &packet){ - if (!pushIsOngoing){waitForStreamPushReady();} - if (nProxy.negTimer > 600){ - WARN_MSG("No negotiation response from buffer - reconnecting."); - nProxy.clear(); - reconnect(); - } - InOutBase::bufferLivePacket(packet); - pushIsOngoing = true; - } - Output::Output(Socket::Connection &conn) : myConn(conn){ pushing = false; pushIsOngoing = false; @@ -76,11 +64,11 @@ namespace Mist{ maxSkipAhead = 7500; uaDelay = 10; realTime = 1000; - lastRecv = Util::epoch(); + lastRecv = Util::bootSecs(); if (myConn){ setBlocking(true); }else{ - DEBUG_MSG(DLVL_WARN, "Warning: MistOut created with closed socket!"); + WARN_MSG("Warning: MistOut created with closed socket!"); } sentHeader = false; isRecordingToFile = false; @@ -128,39 +116,10 @@ namespace Mist{ myConn.setBlocking(isBlocking); } - uint32_t Output::currTrackCount() const{return buffer.size();} - bool Output::isRecording(){ return config->hasOption("target") && config->getString("target").size(); } - void Output::updateMeta(){ - // cancel if not alive or pushing a new stream - if (!nProxy.userClient.isAlive() || (isPushing() && myMeta.tracks.size())){return;} - // read metadata from page to myMeta variable - if (nProxy.metaPages[0].mapped){ - IPC::semaphore *liveSem = 0; - if (!myMeta.vod){ - static char liveSemName[NAME_BUFFER_SIZE]; - snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str()); - liveSem = new IPC::semaphore(liveSemName, O_RDWR, ACCESSPERMS, 8, !myMeta.live); - if (*liveSem){ - liveSem->wait(); - }else{ - delete liveSem; - liveSem = 0; - } - } - DTSC::Packet tmpMeta(nProxy.metaPages[0].mapped, nProxy.metaPages[0].len, true); - if (tmpMeta.getVersion()){myMeta.reinit(tmpMeta);} - if (liveSem){ - liveSem->post(); - delete liveSem; - liveSem = 0; - } - } - } - /// Called when stream initialization has failed. /// The standard implementation will set isInitialized to false and close the client connection, /// thus causing the process to exit cleanly. @@ -179,7 +138,6 @@ namespace Mist{ void Output::initialize(){ MEDIUM_MSG("initialize"); if (isInitialized){return;} - if (nProxy.metaPages[0].mapped){return;} if (streamName.size() < 1){ return; // abort - no stream to initialize... } @@ -208,11 +166,10 @@ namespace Mist{ /// If this happens, the extra calls to the function return instantly. void Output::doSync(bool force){ static bool recursing = false; - if (!statsPage.getData()){return;} + if (!statComm){return;} if (recursing){return;} recursing = true; - IPC::statExchange tmpEx(statsPage.getData()); - if (tmpEx.getSync() == 2 || force){ + if (statComm.getSync() == 2 || force){ if (getStatsName() == capa["name"].asStringRef() && Triggers::shouldTrigger("USER_NEW", streamName)){ // sync byte 0 = no sync yet, wait for sync from controller... char initialSync = 0; @@ -224,7 +181,7 @@ namespace Mist{ if (shmSessions.mapped){ char shmEmpty[SHM_SESSIONS_ITEM]; memset(shmEmpty, 0, SHM_SESSIONS_ITEM); - std::string host = tmpEx.host(); + std::string host = statComm.getHost(); if (host.substr(0, 12) == std::string("\000\000\000\000\000\000\000\000\000\000\377\377", 12)){ char tmpstr[16]; @@ -232,7 +189,7 @@ namespace Mist{ host = tmpstr; }else{ char tmpstr[40]; - snprintf(tmpstr, 40, "%0.2x%0.2x:%0.2x%0.2x:%0.2x%0.2x:%0.2x%0.2x:%0.2x%0.2x:%0.2x%0.2x:%0.2x%0.2x:%0.2x%0.2x", + snprintf(tmpstr, 40, "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", host[0], host[1], host[2], host[3], host[4], host[5], host[6], host[7], host[8], host[9], host[10], host[11], host[12], host[13], host[14], host[15]); host = tmpstr; @@ -241,7 +198,7 @@ namespace Mist{ const std::string &cName = capa["name"].asStringRef(); while (shmOffset + SHM_SESSIONS_ITEM < SHM_SESSIONS_SIZE){ // compare crc - if (*((uint32_t *)(shmSessions.mapped + shmOffset)) == tmpEx.crc()){ + if (Bit::btohl(shmSessions.mapped + shmOffset) == statComm.getCRC()){ // compare stream name if (strncmp(shmSessions.mapped + shmOffset + 4, streamName.c_str(), 100) == 0){ // compare connector @@ -265,40 +222,36 @@ namespace Mist{ if (cacheLock){cacheLock.post();} } unsigned int i = 0; - tmpEx.setSync(initialSync); + statComm.setSync(initialSync); // wait max 10 seconds for sync - while ((!tmpEx.getSync() || tmpEx.getSync() == 2) && i++ < 100 && keepGoing()){ + while ((!statComm.getSync() || statComm.getSync() == 2) && i++ < 100){ Util::wait(100); stats(true); - tmpEx = IPC::statExchange(statsPage.getData()); } - // If we aren't online, skip any further checks. - // We don't immediately return, so the recursing = false line only - // needs to be written once and will always be executed. - if (keepGoing()){ - HIGH_MSG("USER_NEW sync achieved: %u", (unsigned int)tmpEx.getSync()); - // 1 = check requested (connection is new) - if (tmpEx.getSync() == 1){ - std::string payload = streamName + "\n" + getConnectedHost() + "\n" + - JSON::Value(crc).asString() + "\n" + capa["name"].asStringRef() + - "\n" + reqUrl + "\n" + tmpEx.getSessId(); - if (!Triggers::doTrigger("USER_NEW", payload, streamName)){ - onFail("Not allowed to play (USER_NEW)"); - tmpEx.setSync(100); // 100 = denied - }else{ - tmpEx.setSync(10); // 10 = accepted - } + HIGH_MSG("USER_NEW sync achieved: %u", statComm.getSync()); + // 1 = check requested (connection is new) + if (statComm.getSync() == 1){ + std::string payload = streamName + "\n" + getConnectedHost() + "\n" + + JSON::Value(crc).asString() + "\n" + capa["name"].asStringRef() + + "\n" + reqUrl + "\n" + statComm.getSessId(); + if (!Triggers::doTrigger("USER_NEW", payload, streamName)){ + onFail("Not allowed to play (USER_NEW)"); + statComm.setSync(100); // 100 = denied + }else{ + statComm.setSync(10); // 10 = accepted } - // 100 = denied - if (tmpEx.getSync() == 100){onFail("Not allowed to play (USER_NEW cache)");} - if (tmpEx.getSync() == 0){onFail("Not allowed to play (USER_NEW init timeout)", true);} - if (tmpEx.getSync() == 2){ - onFail("Not allowed to play (USER_NEW re-init timeout)", true); - } - // anything else = accepted } + // 100 = denied + if (statComm.getSync() == 100){onFail("Not allowed to play (USER_NEW cache)");} + if (statComm.getSync() == 0){ + onFail("Not allowed to play (USER_NEW init timeout)", true); + } + if (statComm.getSync() == 2){ + onFail("Not allowed to play (USER_NEW re-init timeout)", true); + } + // anything else = accepted }else{ - tmpEx.setSync(10); // auto-accept if no trigger + statComm.setSync(10); // auto-accept if no trigger } } recursing = false; @@ -309,48 +262,47 @@ namespace Mist{ std::string Output::getConnectedBinHost(){return myConn.getBinHost();} bool Output::isReadyForPlay(){ - static bool recursing = false; - if (isPushing() || recursing){return true;} - recursing = true; + // If a protocol does not support any codecs, we assume you know what you're doing + if (!capa.isMember("codecs")){return true;} + if (isPushing()){return true;} if (!isInitialized){initialize();} - if (!myMeta.tracks.size()){updateMeta();} - if (myMeta.tracks.size()){ - if (!selectedTracks.size()){selectDefaultTracks();} - unsigned int mainTrack = getMainSelectedTrack(); - if (mainTrack && myMeta.tracks.count(mainTrack) && - (myMeta.tracks[mainTrack].keys.size() >= 2 || - myMeta.tracks[mainTrack].lastms - myMeta.tracks[mainTrack].firstms > 5000)){ - recursing = false; - return true; + meta.refresh(); + if (getSupportedTracks().size()){ + if (!userSelect.size()){selectDefaultTracks();} + size_t mainTrack = getMainSelectedTrack(); + if (mainTrack != INVALID_TRACK_ID){ + DTSC::Keys keys(M.keys(mainTrack)); + if (keys.getValidCount() >= 2 || M.getLastms(mainTrack) - M.getFirstms(mainTrack) > 5000){ + return true; + } + HIGH_MSG("NOT READY YET (%zu tracks, main track: %zu, with %zu keys)", + M.getValidTracks().size(), getMainSelectedTrack(), keys.getValidCount()); }else{ - HIGH_MSG("NOT READY YET (%lu tracks, %lu = %lu keys)", myMeta.tracks.size(), - getMainSelectedTrack(), myMeta.tracks[getMainSelectedTrack()].keys.size()); + HIGH_MSG("NOT READY YET (%zu tracks)", getSupportedTracks().size()); } }else{ - HIGH_MSG("NOT READY YET (%lu tracks)", myMeta.tracks.size()); + HIGH_MSG("NOT READY (%zu tracks)", getSupportedTracks().size()); } - recursing = false; return false; } /// Disconnects from all stat/user-related shared structures. void Output::disconnect(){ MEDIUM_MSG("disconnect"); - if (statsPage.getData()){ - statsPage.finish(); + if (statComm){ + statComm.unload(); myConn.resetCounter(); } - if (nProxy.userClient.getData()){nProxy.userClient.finish();} + userSelect.clear(); isInitialized = false; - myMeta.reset(); - nProxy.metaPages.clear(); + meta.clear(); } /// Connects or reconnects to the stream. /// Assumes streamName class member has been set already. /// Will start input if not currently active, calls onFail() if this does not succeed. - /// After assuring stream is online, clears nProxy.metaPages, then sets nProxy.metaPages[0], - /// statsPage and nProxy.userClient to (hopefully) valid handles. Finally, calls updateMeta() + /// After assuring stream is online, clears metaPages, then sets metaPages[0], statistics and + /// userClient to (hopefully) valid handles. void Output::reconnect(){ thisPacket.null(); if (config->hasOption("noinput") && config->getBool("noinput")){ @@ -403,112 +355,40 @@ namespace Mist{ } } } + disconnect(); - nProxy.streamName = streamName; - char userPageName[NAME_BUFFER_SIZE]; - snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); + + meta.reInit(streamName, false); unsigned int attempts = 0; - while (!nProxy.userClient.isAlive() && ++attempts < 20 && Util::streamAlive(streamName)){ - nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); + while (!meta && ++attempts < 20 && Util::streamAlive(streamName)){ + meta.reInit(streamName, false); } - if (!nProxy.userClient.isAlive()){ - onFail("Could not register as client", true); - return; - } - char pageId[NAME_BUFFER_SIZE]; - snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); - nProxy.metaPages[0].init(pageId, DEFAULT_STRM_PAGE_SIZE); - if (!nProxy.metaPages[0].mapped){ + if (!meta){ onFail("Could not connect to stream data", true); return; } + meta.refresh(); isInitialized = true; - statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true); + statComm.reload(); stats(true); - updateMeta(); - selectDefaultTracks(); - if (!myMeta.vod && !isReadyForPlay()){ - unsigned long long waitUntil = Util::epoch() + 30; - while (!myMeta.vod && !isReadyForPlay() && nProxy.userClient.isAlive() && keepGoing()){ - if (Util::epoch() > waitUntil + 45 || (!selectedTracks.size() && Util::epoch() > waitUntil)){ + if (!pushing){selectDefaultTracks();} + if (!M.getVod() && !isReadyForPlay()){ + uint64_t waitUntil = Util::epoch() + 30; + while (!M.getVod() && !isReadyForPlay()){ + if (Util::epoch() > waitUntil + 45 || (!userSelect.size() && Util::epoch() > waitUntil)){ INFO_MSG("Giving up waiting for playable tracks. Stream: %s, IP: %s", streamName.c_str(), getConnectedHost().c_str()); break; } - Util::wait(750); + Util::wait(500); stats(); - updateMeta(); } } } - /*LTS-START*/ - /// 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"/"none" 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. - void Output::selectTrack(const std::string &trackType, const std::string &trackVal){ - if (!trackVal.size() || trackVal == "0" || trackVal == "none"){ - return; - }// don't select anything in particular - if (trackVal.find(',') != std::string::npos){ - // Comma-separated list, recurse. - std::stringstream ss(trackVal); - std::string item; - while (std::getline(ss, item, ',')){selectTrack(trackType, item);} - return; - } - uint64_t trackNo = JSON::Value(trackVal).asInt(); - if (trackVal == JSON::Value(trackNo).asString()){ - // It's an integer number - if (!myMeta.tracks.count(trackNo)){ - INFO_MSG("Track %lld does not exist in stream, cannot select", trackNo); - return; - } - const DTSC::Track &Trk = myMeta.tracks[trackNo]; - if (Trk.type != trackType && Trk.codec != trackType){ - INFO_MSG("Track %lld is not %s (%s/%s), cannot select", trackNo, trackType.c_str(), - Trk.type.c_str(), Trk.codec.c_str()); - return; - } - INFO_MSG("Selecting %s track %lld (%s/%s)", trackType.c_str(), trackNo, Trk.type.c_str(), - Trk.codec.c_str()); - selectedTracks.insert(trackNo); - return; - } - std::string trackLow = trackVal; - Util::stringToLower(trackLow); - if (trackLow == "all" || trackLow == "*"){ - // select all tracks of this type - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - const DTSC::Track &Trk = it->second; - if (Trk.type == trackType || Trk.codec == trackType){ - selectedTracks.insert(it->first); - INFO_MSG("Selecting %s track %lu (%s/%s)", trackType.c_str(), it->first, Trk.type.c_str(), - Trk.codec.c_str()); - } - } - return; - } - // attempt to do language/codec matching - // convert 2-character language codes into 3-character language codes - if (trackLow.size() == 2){trackLow = Encodings::ISO639::twoToThree(trackLow);} - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - const DTSC::Track &Trk = it->second; - if (Trk.type == trackType || Trk.codec == trackType){ - std::string codecLow = Trk.codec; - Util::stringToLower(codecLow); - if (Trk.lang == trackLow || trackLow == codecLow){ - selectedTracks.insert(it->first); - INFO_MSG("Selecting %s track %lu (%s/%s)", trackType.c_str(), it->first, Trk.type.c_str(), - Trk.codec.c_str()); - } - } - } + std::set Output::getSupportedTracks(const std::string &type) const{ + return Util::getSupportedTracks(M, capa, type); } - /*LTS-END*/ /// Automatically selects the tracks that are possible and/or wanted. /// Returns true if the track selection changed in any way. @@ -518,6 +398,8 @@ namespace Mist{ if (!isInitialized){return false;} } + meta.refresh(); + bool autoSeek = buffer.size(); uint64_t seekTarget = currentTime(); std::set newSelects = @@ -534,88 +416,63 @@ namespace Mist{ return false; } - // We changed the selection! Change to the new selection. - selectedTracks.clear(); - for (std::set::iterator reselIt = newSelects.begin(); reselIt != newSelects.end(); ++reselIt){ - selectedTracks.insert(*reselIt); + if (autoSeek){ + std::set toRemove; + for (std::set::iterator it = wouldSelect.begin(); it != wouldSelect.end(); it++){ + // autoSeeking and target not in bounds? Drop it too. + if (M.getLastms(*it) < std::max(seekTarget, (uint64_t)6000lu) - 6000){ + toRemove.insert(*it); + } + } + // remove those from selectedtracks + for (std::set::iterator it = toRemove.begin(); it != toRemove.end(); it++){ + wouldSelect.erase(*it); + } } - if (autoSeek){ - INFO_MSG("Automatically seeking to position %llu to resume playback", seekTarget); + // First, back up and wipe the existing selections, if any. + std::map oldSelect = userSelect; + userSelect.clear(); + + // Select tracks here! + for (std::set::iterator it = wouldSelect.begin(); it != wouldSelect.end(); it++){ + userSelect[*it].reload(streamName, *it); + } + + bool madeChange = (oldSelect != userSelect); + if (autoSeek && madeChange){ + INFO_MSG("Automatically seeking to position %" PRIu64 " to resume playback", seekTarget); seek(seekTarget); } - return true; + return madeChange; } /// Clears the buffer, sets parseData to false, and generally makes not very much happen at all. void Output::stop(){ buffer.clear(); parseData = false; - sought = false; } - unsigned int Output::getKeyForTime(long unsigned int trackId, long long timeStamp){ - DTSC::Track &trk = myMeta.tracks[trackId]; - if (!trk.keys.size()){return 0;} - unsigned int keyNo = trk.keys.begin()->getNumber(); - unsigned int partCount = 0; - std::deque::iterator it; - for (it = trk.keys.begin(); it != trk.keys.end() && it->getTime() <= timeStamp; it++){ - keyNo = it->getNumber(); - partCount += it->getParts(); + uint64_t Output::pageNumForKey(size_t trackId, size_t keyNum){ + const Util::RelAccX &tPages = M.pages(trackId); + for (uint64_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ + uint64_t pageNum = tPages.getInt("firstkey", i); + if (pageNum > keyNum) break; + uint64_t pageKeys = tPages.getInt("keycount", i); + if (keyNum > pageNum + pageKeys - 1) continue; + uint64_t pageAvail = tPages.getInt("avail", i); + return pageAvail == 0 ? INVALID_PAGE_NUM : pageNum; } - // if the time is before the next keyframe but after the last part, correctly seek to next keyframe - if (partCount && it != trk.keys.end() && timeStamp > it->getTime() - trk.parts[partCount - 1].getDuration()){ - ++keyNo; - } - return keyNo; - } - - /// Returns the timestamp of the next upcoming keyframe after thisPacket, or 0 if that cannot be determined (yet). - uint64_t Output::nextKeyTime(){ - DTSC::Track &trk = myMeta.tracks[getMainSelectedTrack()]; - if (!trk.keys.size()){return 0;} - std::deque::iterator it; - for (it = trk.keys.begin(); it != trk.keys.end(); it++){ - if (it->getTime() > lastPacketTime){return it->getTime();} - } - return 0; - } - - int Output::pageNumForKey(long unsigned int trackId, long long int keyNum){ - if (!nProxy.metaPages.count(trackId) || !nProxy.metaPages[trackId].mapped){ - char id[NAME_BUFFER_SIZE]; - snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId); - nProxy.metaPages[trackId].init(id, SHM_TRACK_INDEX_SIZE); - } - if (!nProxy.metaPages[trackId].mapped){return -1;} - int len = nProxy.metaPages[trackId].len / 8; - for (int i = 0; i < len; i++){ - char *tmpOffset = nProxy.metaPages[trackId].mapped + (i * 8); - long amountKey = Bit::btohl(tmpOffset + 4); - if (amountKey == 0){continue;} - long tmpKey = Bit::btohl(tmpOffset); - if (tmpKey <= keyNum && ((tmpKey ? tmpKey : 1) + amountKey) > keyNum){return tmpKey;} - } - return -1; + return INVALID_PAGE_NUM; } /// Gets the highest page number available for the given trackId. - int Output::pageNumMax(long unsigned int trackId){ - if (!nProxy.metaPages.count(trackId) || !nProxy.metaPages[trackId].mapped){ - char id[NAME_BUFFER_SIZE]; - snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId); - nProxy.metaPages[trackId].init(id, SHM_TRACK_INDEX_SIZE); - } - if (!nProxy.metaPages[trackId].mapped){return -1;} - int len = nProxy.metaPages[trackId].len / 8; - int highest = -1; - for (int i = 0; i < len; i++){ - char *tmpOffset = nProxy.metaPages[trackId].mapped + (i * 8); - long amountKey = Bit::btohl(tmpOffset + 4); - if (amountKey == 0){continue;} - long tmpKey = Bit::btohl(tmpOffset); - if (tmpKey > highest){highest = tmpKey;} + uint64_t Output::pageNumMax(size_t trackId){ + const Util::RelAccX &tPages = M.pages(trackId); + uint64_t highest = 0; + for (uint64_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ + uint64_t pageNum = tPages.getInt("firstkey", i); + if (pageNum > highest){highest = pageNum;} } return highest; } @@ -623,42 +480,50 @@ namespace Mist{ /// Loads the page for the given trackId and keyNum into memory. /// Overwrites any existing page for the same trackId. /// Automatically calls thisPacket.null() if necessary. - void Output::loadPageForKey(long unsigned int trackId, long long int keyNum){ - if (!myMeta.tracks.count(trackId) || !myMeta.tracks[trackId].keys.size()){ - WARN_MSG("Load for track %lu key %lld aborted - track is empty", trackId, keyNum); + void Output::loadPageForKey(size_t trackId, size_t keyNum){ + if (!M.trackValid(trackId)){ + WARN_MSG("Load for track %zu key %zu aborted - track does not exist", trackId, keyNum); return; } - if (myMeta.vod && keyNum > myMeta.tracks[trackId].keys.rbegin()->getNumber()){ - INFO_MSG("Load for track %lu key %lld aborted, is > %lld", trackId, keyNum, - myMeta.tracks[trackId].keys.rbegin()->getNumber()); - nProxy.curPage.erase(trackId); - currKeyOpen.erase(trackId); + if (!M.trackLoaded(trackId)){meta.refresh();} + DTSC::Keys keys(M.keys(trackId)); + if (!keys.getValidCount()){ + WARN_MSG("Load for track %zu key %zu aborted - track is empty", trackId, keyNum); return; } - VERYHIGH_MSG("Loading track %lu, containing key %lld", trackId, keyNum); - unsigned int timeout = 0; - unsigned long pageNum = pageNumForKey(trackId, keyNum); - while (keepGoing() && pageNum == -1){ - if (!timeout){HIGH_MSG("Requesting page with key %lu:%lld", trackId, keyNum);} + size_t lastAvailKey = keys.getEndValid() - 1; + if (meta.getVod() && keyNum > lastAvailKey){ + INFO_MSG("Load for track %zu key %zu aborted, is > %zu", trackId, keyNum, lastAvailKey); + curPage.erase(trackId); + currentPage.erase(trackId); + return; + } + VERYHIGH_MSG("Loading track %zu, containing key %zu", trackId, keyNum); + uint32_t timeout = 0; + uint64_t pageNum = pageNumForKey(trackId, keyNum); + while (keepGoing() && pageNum == INVALID_PAGE_NUM){ + if (!timeout){HIGH_MSG("Requesting page with key %zu:%zu", trackId, keyNum);} ++timeout; // if we've been waiting for this page for 3 seconds, reconnect to the stream - something might be going wrong... if (timeout == 30){ DEVEL_MSG("Loading is taking longer than usual, reconnecting to stream %s...", streamName.c_str()); reconnect(); + if (!meta){return;} } if (timeout > 100){ - FAIL_MSG("Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId); - nProxy.curPage.erase(trackId); - currKeyOpen.erase(trackId); + FAIL_MSG("Timeout while waiting for requested page %zu for track %zu. Aborting.", pageNum, trackId); + curPage.erase(trackId); + currentPage.erase(trackId); return; } - if (keyNum){ - nxtKeyNum[trackId] = keyNum - 1; + if (!userSelect.count(trackId)){ + WARN_MSG("Loading page for non-selected track %zu", trackId); }else{ - nxtKeyNum[trackId] = 0; + userSelect[trackId].setKeyNum(keyNum); } + stats(true); - playbackSleep(100); + playbackSleep(50); pageNum = pageNumForKey(trackId, keyNum); } @@ -667,25 +532,26 @@ namespace Mist{ return; } - if (keyNum){ - nxtKeyNum[trackId] = keyNum - 1; + if (!userSelect.count(trackId)){ + WARN_MSG("Loading page for non-selected track %zu", trackId); }else{ - nxtKeyNum[trackId] = 0; + userSelect[trackId].setKeyNum(keyNum); } + stats(true); - if (currKeyOpen.count(trackId) && currKeyOpen[trackId] == (unsigned int)pageNum){return;} + if (currentPage.count(trackId) && currentPage[trackId] == pageNum){return;} // If we're loading the track thisPacket is on, null it to prevent accesses. - if (thisPacket && thisPacket.getTrackId() == trackId){thisPacket.null();} + if (thisPacket && thisIdx == trackId){thisPacket.null();} char id[NAME_BUFFER_SIZE]; snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackId, pageNum); - nProxy.curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE); - if (!(nProxy.curPage[trackId].mapped)){ - FAIL_MSG("Initializing page %s failed", nProxy.curPage[trackId].name.c_str()); - currKeyOpen.erase(trackId); + curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE); + if (!(curPage[trackId].mapped)){ + FAIL_MSG("Initializing page %s failed", curPage[trackId].name.c_str()); + currentPage.erase(trackId); return; } - currKeyOpen[trackId] = pageNum; + currentPage[trackId] = pageNum; VERYHIGH_MSG("Page %s loaded for %s", id, streamName.c_str()); } @@ -699,41 +565,39 @@ namespace Mist{ /// Returns the start time of earliest track if nothing is selected. /// Returns zero if no tracks exist. uint64_t Output::startTime(){ - if (!myMeta.tracks.size()){return 0;} + std::set validTracks = M.getValidTracks(); + if (!validTracks.size()){return 0;} uint64_t start = 0xFFFFFFFFFFFFFFFFull; - if (selectedTracks.size()){ - for (std::set::iterator it = selectedTracks.begin(); - it != selectedTracks.end(); it++){ - if (myMeta.tracks.count(*it)){ - if (start > myMeta.tracks[*it].firstms){start = myMeta.tracks[*it].firstms;} + if (userSelect.size()){ + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (M.trackValid(it->first) && start > M.getFirstms(it->first)){ + start = M.getFirstms(it->first); } } }else{ - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (start > it->second.firstms){start = it->second.firstms;} + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (start > M.getFirstms(*it)){start = M.getFirstms(*it);} } } return start; } - /// Return the end time of the selected tracks, or 0 if unknown. + /// Return the end time of the selected tracks, or 0 if unknown or live. /// Returns the end time of latest track if nothing is selected. /// Returns zero if no tracks exist. uint64_t Output::endTime(){ - if (!myMeta.tracks.size()){return 0;} + std::set validTracks = M.getValidTracks(); + if (!validTracks.size()){return 0;} uint64_t end = 0; - if (selectedTracks.size()){ - for (std::set::iterator it = selectedTracks.begin(); - it != selectedTracks.end(); it++){ - if (myMeta.tracks.count(*it)){ - if (end < myMeta.tracks[*it].lastms){end = myMeta.tracks[*it].lastms;} + if (userSelect.size()){ + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (M.trackValid(it->first) && end < M.getLastms(it->first)){ + end = meta.getLastms(it->first); } } }else{ - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (end < it->second.lastms){end = it->second.lastms;} + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (end < meta.getLastms(*it)){end = meta.getLastms(*it);} } } return end; @@ -743,129 +607,152 @@ namespace Mist{ /// Returns the time stamp of the newest track if nothing is selected. /// Returns zero if no tracks exist. uint64_t Output::liveTime(){ - if (!myMeta.live){return 0;} - if (!myMeta.tracks.size()){return 0;} + if (M.getVod()){return 0;} + if (M.getValidTracks().size()){return 0;} uint64_t end = 0; - if (selectedTracks.size()){ - for (std::set::iterator it = selectedTracks.begin(); - it != selectedTracks.end(); it++){ - if (myMeta.tracks.count(*it)){ - if (end < myMeta.tracks[*it].lastms){end = myMeta.tracks[*it].lastms;} - } + if (userSelect.size()){ + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (end < M.getLastms(it->first)){end = M.getLastms(it->first);} } }else{ - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (end < it->second.lastms){end = it->second.lastms;} + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (end < M.getLastms(*it)){end = M.getLastms(*it);} } } return end; } /// Prepares all tracks from selectedTracks for seeking to the specified ms position. - /// If toKey is true, clips the seek to the nearest keyframe if the main track is a video track. - void Output::seek(unsigned long long pos, bool toKey){ + void Output::seekKeyframesIn(unsigned long long pos, unsigned long long maxDelta){ + sought = true; + if (!isInitialized){initialize();} + buffer.clear(); + thisPacket.null(); + MEDIUM_MSG("Seeking keyframes near %llums, max delta of %llu", pos, maxDelta); + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (!M.getValidTracks().count(it->first)){continue;} + uint64_t time = M.getTimeForKeyIndex(it->first, M.getKeyIndexForTime(it->first, pos)); + uint64_t timeDelta = M.getTimeForKeyIndex(it->first, M.getKeyIndexForTime(it->first, pos + maxDelta)); + if (time >= (pos - maxDelta)){ + pos = time; + }else if (timeDelta >= (pos - maxDelta)){ + pos = timeDelta; + } + seek(it->first, pos, false); + } + } + + /// Prepares all tracks from selectedTracks for seeking to the specified ms position. + /// If toKey is true, clips the seek to the nearest keyframe if the main track is a video track. + void Output::seek(uint64_t pos, bool toKey){ sought = true; if (!isInitialized){initialize();} buffer.clear(); thisPacket.null(); - if (myMeta.live){updateMeta();} if (toKey){ - long unsigned int mainTrack = getMainSelectedTrack(); - // abort toKey if there are no keys in the main track - if (!myMeta.tracks.count(mainTrack) || !myMeta.tracks[mainTrack].keys.size()){ + size_t mainTrack = getMainSelectedTrack(); + if (mainTrack == INVALID_TRACK_ID){ WARN_MSG("Sync-seeking impossible (main track invalid); performing regular seek instead"); seek(pos); return; } - DTSC::Track &Trk = myMeta.tracks[mainTrack]; - if (Trk.type == "video"){ - unsigned long long seekPos = 0; - for (std::deque::iterator it = Trk.keys.begin(); it != Trk.keys.end(); ++it){ - unsigned long long currPos = it->getTime(); - if (currPos > pos){break;}// stop if we're past the point we wanted - seekPos = currPos; - } - pos = seekPos; + if (M.getType(mainTrack) == "video"){ + DTSC::Keys keys(M.keys(mainTrack)); + size_t keyNum = keys.getNumForTime(pos); + pos = keys.getTime(keyNum); } } - MEDIUM_MSG("Seeking to %llums", pos); - std::set seekTracks = selectedTracks; - for (std::set::iterator it = seekTracks.begin(); it != seekTracks.end(); it++){ - if (myMeta.tracks.count(*it)){seek(*it, pos);} + MEDIUM_MSG("Seeking to %" PRIu64 "ms (%s)", pos, toKey ? "sync" : "direct"); + std::set seekTracks; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + seekTracks.insert(it->first); } - firstTime = Util::getMS() - buffer.begin()->time; + for (std::set::iterator it = seekTracks.begin(); it != seekTracks.end(); it++){ + seek(*it, pos, false); + } + firstTime = Util::bootMS() - currentTime(); } - bool Output::seek(unsigned int tid, unsigned long long pos, bool getNextKey){ - if (myMeta.live && myMeta.tracks[tid].lastms < pos){ - unsigned int maxTime = 0; - while (myMeta.tracks[tid].lastms < pos && myConn && ++maxTime <= 20 && keepGoing()){ - Util::wait(500); - stats(); - updateMeta(); - } - } - if (myMeta.tracks[tid].lastms < pos){ - WARN_MSG("Aborting seek to %llums in track %u: past end of track (= %llums).", pos, tid, - myMeta.tracks[tid].lastms); - selectedTracks.erase(tid); + bool Output::seek(size_t tid, uint64_t pos, bool getNextKey){ + if (!M.trackValid(tid)){ + MEDIUM_MSG("Aborting seek to %" PRIu64 "ms in track %zu: Invalid track id.", pos, tid); + userSelect.erase(tid); return false; } - unsigned int keyNum = getKeyForTime(tid, pos); - if (myMeta.tracks[tid].getKey(keyNum).getTime() > pos){ - if (myMeta.live){ - WARN_MSG("Actually seeking to %d, for %d is not available any more", - myMeta.tracks[tid].getKey(keyNum).getTime(), pos); - pos = myMeta.tracks[tid].getKey(keyNum).getTime(); + if (!M.trackLoaded(tid)){meta.refresh();} + + HIGH_MSG("Seeking for pos %" PRIu64, pos); + if (meta.getLive() && meta.getLastms(tid) < pos){ + unsigned int maxTime = 0; + while (meta.getLastms(tid) < pos && myConn && ++maxTime <= 20){ + Util::wait(500); + stats(); + } + } + if (meta.getLastms(tid) <= pos){ + WARN_MSG("Aborting seek to %" PRIu64 "ms in track %zu: past end of track (= %" PRIu64 "ms).", + pos, tid, meta.getLastms(tid)); + userSelect.erase(tid); + return false; + } + DTSC::Keys keys(M.keys(tid)); + size_t keyNum = keys.getNumForTime(pos); + uint64_t actualKeyTime = keys.getTime(keyNum); + HIGH_MSG("Seeking to track %zu key %zu => time %" PRIu64, tid, keyNum, pos); + if (actualKeyTime > pos){ + if (M.getLive()){ + WARN_MSG("Actually seeking to %" PRIu64 ", for %" PRIu64 " is not available any more", + actualKeyTime, pos); + pos = actualKeyTime; + userSelect[tid].setKeyNum(keyNum); } } loadPageForKey(tid, keyNum + (getNextKey ? 1 : 0)); - if (!nProxy.curPage.count(tid) || !nProxy.curPage[tid].mapped){ - WARN_MSG("Aborting seek to %llums in track %u: not available.", pos, tid); - selectedTracks.erase(tid); + if (!curPage.count(tid) || !curPage[tid].mapped){ + WARN_MSG("Aborting seek to %" PRIu64 "ms in track %zu: not available.", pos, tid); + userSelect.erase(tid); return false; } sortedPageInfo tmp; tmp.tid = tid; tmp.offset = 0; + tmp.partIndex = 0; DTSC::Packet tmpPack; - tmpPack.reInit(nProxy.curPage[tid].mapped + tmp.offset, 0, true); + tmpPack.reInit(curPage[tid].mapped + tmp.offset, 0, true); tmp.time = tmpPack.getTime(); - char *mpd = nProxy.curPage[tid].mapped; - while ((long long)tmp.time < pos && tmpPack){ + char *mpd = curPage[tid].mapped; + while (tmp.time < pos && tmpPack){ tmp.offset += tmpPack.getDataLen(); tmpPack.reInit(mpd + tmp.offset, 0, true); tmp.time = tmpPack.getTime(); } if (tmpPack){ - HIGH_MSG("Sought to time %llu in %s@%u", tmp.time, streamName.c_str(), tid); + HIGH_MSG("Sought to time %" PRIu64 " (yields a packet at %" PRIu64 "ms) in %s@%zu", tmp.time, + tmpPack.getTime(), streamName.c_str(), tid); + tmp.partIndex = M.getPartIndex(tmpPack, tmp.tid); buffer.insert(tmp); return true; - }else{ - // don't print anything for empty packets - not sign of corruption, just unfinished stream. - if (nProxy.curPage[tid].mapped[tmp.offset] != 0){ - FAIL_MSG("Noes! Couldn't find packet on track %d because of some kind of corruption error " - "or somesuch.", - tid); - }else{ - VERYHIGH_MSG("Track %d no data (key %u @ %u) - waiting...", tid, - getKeyForTime(tid, pos) + (getNextKey ? 1 : 0), tmp.offset); - unsigned int i = 0; - while (!myMeta.live && nProxy.curPage[tid].mapped[tmp.offset] == 0 && ++i <= 10 && keepGoing()){ - Util::wait(100 * i); - stats(); - } - if (nProxy.curPage[tid].mapped[tmp.offset] == 0){ - FAIL_MSG("Track %d no data (key %u@%u) - timeout", tid, - getKeyForTime(tid, pos) + (getNextKey ? 1 : 0), tmp.offset); - }else{ - return seek(tid, pos, getNextKey); - } - } - selectedTracks.erase(tid); + } + // don't print anything for empty packets - not sign of corruption, just unfinished stream. + if (curPage[tid].mapped[tmp.offset] != 0){ + FAIL_MSG("Noes! Couldn't find packet on track %zu because of some kind of corruption error " + "or somesuch.", + tid); return false; } + VERYHIGH_MSG("Track %zu no data (key %zu @ %" PRIu64 ") - waiting...", tid, + keyNum + (getNextKey ? 1 : 0), tmp.offset); + uint32_t i = 0; + while (!meta.getLive() && curPage[tid].mapped[tmp.offset] == 0 && ++i <= 10){ + Util::wait(100 * i); + stats(); + } + if (curPage[tid].mapped[tmp.offset]){return seek(tid, pos, getNextKey);} + FAIL_MSG("Track %zu no data (key %zu@%" PRIu64 ") - timeout", tid, keyNum + (getNextKey ? 1 : 0), tmp.offset); + userSelect.erase(tid); + firstTime = Util::bootMS() - buffer.begin()->time; + return false; } /// This function decides where in the stream initial playback starts. @@ -874,34 +761,43 @@ namespace Mist{ /// needsLookAhead+minKeepAway ms from the end. Unless lastms < 5000, then it seeks to the first /// keyframe of the main track. Aborts if there is no main track or it has no keyframes. void Output::initialSeek(){ - unsigned long long seekPos = 0; - if (myMeta.live){ - long unsigned int mainTrack = getMainSelectedTrack(); - // cancel if there are no keys in the main track - if (!myMeta.tracks.count(mainTrack) || !myMeta.tracks[mainTrack].keys.size()){return;} + uint64_t seekPos = 0; + if (meta.getLive()){ + size_t mainTrack = getMainSelectedTrack(); + if (mainTrack == INVALID_TRACK_ID){return;} + DTSC::Keys keys(M.keys(mainTrack)); + if (!keys.getValidCount()){return;} // seek to the newest keyframe, unless that is <5s, then seek to the oldest keyframe - for (std::deque::reverse_iterator it = myMeta.tracks[mainTrack].keys.rbegin(); - it != myMeta.tracks[mainTrack].keys.rend(); ++it){ - seekPos = it->getTime(); + uint32_t firstKey = keys.getFirstValid(); + uint32_t lastKey = keys.getEndValid() - 1; + for (int64_t i = lastKey; i >= firstKey; i--){ + seekPos = keys.getTime(i); if (seekPos < 5000){continue;}// if we're near the start, skip back bool good = true; // check if all tracks have data for this point in time - for (std::set::iterator ti = selectedTracks.begin(); ti != selectedTracks.end(); ++ti){ - if (!myMeta.tracks.count(*ti)){ - HIGH_MSG("Skipping track %lu, not in tracks", *ti); - continue; - }// ignore missing tracks - DTSC::Track &thisTrack = myMeta.tracks[*ti]; - if (thisTrack.lastms < seekPos + needsLookAhead + extraKeepAway + thisTrack.minKeepAway){ + for (std::map::iterator ti = userSelect.begin(); ti != userSelect.end(); ++ti){ + if (meta.getLastms(ti->first) < seekPos + needsLookAhead){ good = false; break; } - if (mainTrack == *ti){continue;}// skip self - if (thisTrack.lastms == thisTrack.firstms){ - HIGH_MSG("Skipping track %lu, last equals first", *ti); + if (mainTrack == ti->first){continue;}// skip self + if (!M.trackValid(ti->first)){ + HIGH_MSG("Skipping track %lu, not in tracks", ti->first); + continue; + }// ignore missing tracks + if (M.getLastms(ti->first) < seekPos + needsLookAhead + extraKeepAway + M.getMinKeepAway(ti->first)){ + good = false; + break; + } + if (meta.getLastms(ti->first) == M.getFirstms(ti->first)){ + HIGH_MSG("Skipping track %lu, last equals first", ti->first); continue; }// ignore point-tracks - HIGH_MSG("Track %lu is good", *ti); + if (meta.getLastms(ti->first) < seekPos){ + good = false; + break; + } + HIGH_MSG("Track %zu is good", ti->first); } // if yes, seek here if (good){break;} @@ -909,56 +805,63 @@ namespace Mist{ } /*LTS-START*/ if (isRecordingToFile){ - if (myMeta.live && + if (M.getLive() && (targetParams.count("recstartunix") || targetParams.count("recstopunix"))){ uint64_t unixStreamBegin = Util::epoch() - (liveTime() / 1000); if (targetParams.count("recstartunix")){ - long long startUnix = atoll(targetParams["recstartunix"].c_str()); + uint64_t startUnix = atoll(targetParams["recstartunix"].c_str()); if (startUnix < unixStreamBegin){ WARN_MSG( "Recording start time is earlier than stream begin - starting earliest possible"); targetParams["recstart"] = "-1"; }else{ - targetParams["recstart"] = - JSON::Value((int64_t)((startUnix - unixStreamBegin) * 1000)).asString(); + targetParams["recstart"] = JSON::Value((startUnix - unixStreamBegin) * 1000).asString(); } } if (targetParams.count("recstopunix")){ - long long stopUnix = atoll(targetParams["recstopunix"].c_str()); + uint64_t stopUnix = atoll(targetParams["recstopunix"].c_str()); if (stopUnix < unixStreamBegin){ onFail("Recording stop time is earlier than stream begin - aborting", true); return; }else{ - targetParams["recstop"] = JSON::Value((int64_t)((stopUnix - unixStreamBegin) * 1000)).asString(); + targetParams["recstop"] = JSON::Value((stopUnix - unixStreamBegin) * 1000).asString(); } } } + if (targetParams.count("recstop")){ + uint64_t endRec = atoll(targetParams["recstop"].c_str()); + if (endRec < startTime()){ + onFail("Entire recording range is in the past", true); + return; + } + INFO_MSG("Recording will stop at %" PRIu64, endRec); + } if (targetParams.count("recstart") && atoll(targetParams["recstart"].c_str()) != 0){ - unsigned long int mainTrack = getMainSelectedTrack(); - long long startRec = atoll(targetParams["recstart"].c_str()); - if (startRec > 0 && startRec > myMeta.tracks[mainTrack].lastms){ - if (myMeta.vod){ + size_t mainTrack = getMainSelectedTrack(); + uint64_t startRec = atoll(targetParams["recstart"].c_str()); + if (startRec > M.getLastms(mainTrack)){ + if (M.getVod()){ onFail("Recording start past end of non-live source", true); return; } - INFO_MSG("Waiting for stream to reach recording starting point"); - long unsigned int streamAvail = myMeta.tracks[mainTrack].lastms; - long unsigned int lastUpdated = Util::getMS(); + uint64_t streamAvail = M.getLastms(mainTrack); + uint64_t lastUpdated = Util::getMS(); while (Util::getMS() - lastUpdated < 5000 && startRec > streamAvail && keepGoing()){ Util::sleep(500); - updateMeta(); - if (myMeta.tracks[mainTrack].lastms > streamAvail){ + if (M.getLastms(mainTrack) > streamAvail){ stats(); - streamAvail = myMeta.tracks[mainTrack].lastms; + streamAvail = M.getLastms(mainTrack); lastUpdated = Util::getMS(); } } } - if (startRec < 0 || startRec < startTime()){ - WARN_MSG("Record begin at %llu ms not available, starting at %llu ms instead", startRec, startTime()); + if (startRec < startTime()){ + WARN_MSG("Record begin at %" PRIu64 " ms not available, starting at %" PRIu64 + " ms instead", + startRec, startTime()); startRec = startTime(); } - INFO_MSG("Recording will start at %lld", startRec); + INFO_MSG("Recording will start at %" PRIu64, startRec); seekPos = startRec; } // Duration to record in seconds. Overrides recstop. @@ -980,23 +883,22 @@ namespace Mist{ INFO_MSG("Recording will stop at %lld", endRec); } }else{ - if (myMeta.live && targetParams.count("pushdelay")){ + if (M.getLive() && targetParams.count("pushdelay")){ INFO_MSG("Converting pushdelay syntax into corresponding startunix+realtime options"); targetParams["startunix"] = std::string("-") + targetParams["pushdelay"]; targetParams["realtime"] = "1"; } - if (myMeta.live && (targetParams.count("startunix") || targetParams.count("stopunix"))){ - uint64_t unixStreamBegin = Util::epoch() - (liveTime() / 1000); + if (M.getLive() && (targetParams.count("startunix") || targetParams.count("stopunix"))){ + int64_t unixStreamBegin = Util::epoch() - (liveTime() / 1000); if (targetParams.count("startunix")){ - long long startUnix = atoll(targetParams["startunix"].c_str()); + int64_t startUnix = atoll(targetParams["startunix"].c_str()); if (startUnix < 0){ - long long origStartUnix = startUnix; + int64_t origStartUnix = startUnix; startUnix += Util::epoch(); if (startUnix < unixStreamBegin){ INFO_MSG("Waiting for stream to reach intended starting point"); while (startUnix < Util::epoch() - (liveTime() / 1000) && keepGoing()){ Util::wait(1000); - updateMeta(); stats(); startUnix = origStartUnix + Util::epoch(); } @@ -1004,108 +906,142 @@ namespace Mist{ } if (startUnix < unixStreamBegin){ WARN_MSG("Start time is earlier than stream begin - starting earliest possible"); + WARN_MSG("%" PRId64 " < %" PRId64, startUnix, unixStreamBegin); targetParams["start"] = "-1"; }else{ - targetParams["start"] = JSON::Value((int64_t)((startUnix - unixStreamBegin) * 1000)).asString(); + targetParams["start"] = JSON::Value((startUnix - unixStreamBegin) * 1000).asString(); } } if (targetParams.count("stopunix")){ - long long stopUnix = atoll(targetParams["stopunix"].c_str()); + int64_t stopUnix = atoll(targetParams["stopunix"].c_str()); if (stopUnix < 0){stopUnix += Util::epoch();} if (stopUnix < unixStreamBegin){ onFail("Stop time is earlier than stream begin - aborting", true); return; }else{ - targetParams["stop"] = JSON::Value((int64_t)((stopUnix - unixStreamBegin) * 1000)).asString(); + targetParams["stop"] = JSON::Value((stopUnix - unixStreamBegin) * 1000).asString(); } } } if (targetParams.count("stop")){ - long long endRec = atoll(targetParams["stop"].c_str()); + int64_t endRec = atoll(targetParams["stop"].c_str()); if (endRec < 0 || endRec < startTime()){ onFail("Entire range is in the past", true); return; } - INFO_MSG("Playback will stop at %lld", endRec); + INFO_MSG("Playback will stop at %" PRIu64, endRec); } if (targetParams.count("start") && atoll(targetParams["start"].c_str()) != 0){ - unsigned long int mainTrack = getMainSelectedTrack(); - long long startRec = atoll(targetParams["start"].c_str()); - if (startRec > 0 && startRec > myMeta.tracks[mainTrack].lastms){ - if (myMeta.vod){ + size_t mainTrack = getMainSelectedTrack(); + int64_t startRec = atoll(targetParams["start"].c_str()); + if (startRec > M.getLastms(mainTrack)){ + if (M.getVod()){ onFail("Playback start past end of non-live source", true); return; } INFO_MSG("Waiting for stream to reach playback starting point"); - long unsigned int streamAvail = myMeta.tracks[mainTrack].lastms; - long unsigned int lastUpdated = Util::getMS(); + int64_t streamAvail = M.getLastms(mainTrack); + int64_t lastUpdated = Util::getMS(); while (Util::getMS() - lastUpdated < 5000 && startRec > streamAvail && keepGoing()){ Util::sleep(500); - updateMeta(); - if (myMeta.tracks[mainTrack].lastms > streamAvail){ + if (M.getLastms(mainTrack) > streamAvail){ stats(); - streamAvail = myMeta.tracks[mainTrack].lastms; + streamAvail = M.getLastms(mainTrack); lastUpdated = Util::getMS(); } } } if (startRec < 0 || startRec < startTime()){ - WARN_MSG("Playback begin at %llu ms not available, starting at %llu ms instead", startRec, - startTime()); + WARN_MSG("Playback begin at %" PRId64 " ms not available, starting at %" PRIu64 + " ms instead", + startRec, startTime()); startRec = startTime(); } - INFO_MSG("Playback will start at %lld", startRec); + INFO_MSG("Playback will start at %" PRIu64, startRec); seekPos = startRec; } } /*LTS-END*/ - MEDIUM_MSG("Initial seek to %llums", seekPos); + MEDIUM_MSG("Initial seek to %" PRIu64 "ms", seekPos); seek(seekPos); } + /// Returns the highest getMinKeepAway of all selected tracks + uint64_t Output::getMinKeepAway(){ + uint64_t r = 0; + for (std::map::iterator ti = userSelect.begin(); ti != userSelect.end(); ++ti){ + if (ti->first == INVALID_TRACK_ID){continue;} + if (M.getMinKeepAway(ti->first) > r){r = M.getMinKeepAway(ti->first);} + } + return r; + } + /// This function attempts to forward playback in live streams to a more live point. /// It seeks to the last sync'ed keyframe of the main track, no closer than needsLookAhead+minKeepAway ms from the end. /// Aborts if not live, there is no main track or it has no keyframes. bool Output::liveSeek(){ static uint32_t seekCount = 2; - unsigned long long seekPos = 0; - if (!myMeta.live){return false;} - if (isRecordingToFile){return false;} - long unsigned int mainTrack = getMainSelectedTrack(); + uint64_t seekPos = 0; + if (!meta.getLive()){return false;} + size_t mainTrack = getMainSelectedTrack(); + if (mainTrack == INVALID_TRACK_ID){return false;} + uint64_t lMs = meta.getLastms(mainTrack); + uint64_t cTime = thisPacket.getTime(); + uint64_t mKa = getMinKeepAway(); + if (!maxSkipAhead){ + uint64_t newSpeed = 1000; + if (lMs - mKa - needsLookAhead - extraKeepAway > cTime + 50){ + // We need to speed up! + uint64_t diff = (lMs - mKa - needsLookAhead - extraKeepAway) - cTime; + if (diff > 1000){ + newSpeed = 750; + }else if (diff > 500){ + newSpeed = 900; + }else{ + newSpeed = 950; + } + } + if (realTime != newSpeed){ + HIGH_MSG("Changing playback speed from %" PRIu64 " to %" PRIu64 "(%" PRIu64 + " ms LA, %" PRIu64 " ms mKA, %lu eKA)", + realTime, newSpeed, needsLookAhead, mKa, extraKeepAway); + firstTime = Util::bootMS() - cTime; + realTime = newSpeed; + } + return false; + } // cancel if there are no keys in the main track - if (!myMeta.tracks.count(mainTrack)){return false;} - DTSC::Track &mainTrk = myMeta.tracks[mainTrack]; - if (!mainTrk.keys.size()){return false;} + if (mainTrack == INVALID_TRACK_ID){return false;} + DTSC::Keys mainKeys(meta.keys(mainTrack)); + if (!mainKeys.getValidCount()){return false;} - for (std::deque::reverse_iterator it = myMeta.tracks[mainTrack].keys.rbegin(); - it != myMeta.tracks[mainTrack].keys.rend(); ++it){ - seekPos = it->getTime(); - // Only skip forward if we can win a decent amount - if (seekPos <= thisPacket.getTime() + 250 * seekCount){break;} + for (size_t keyNum = mainKeys.getEndValid() - 1; keyNum >= mainKeys.getFirstValid(); keyNum--){ + seekPos = mainKeys.getTime(keyNum); + // Only skip forward if we can win a decent amount (100ms) + if (seekPos <= cTime + 100 * seekCount){break;} bool good = true; // check if all tracks have data for this point in time - for (std::set::iterator ti = selectedTracks.begin(); ti != selectedTracks.end(); ++ti){ - if (!myMeta.tracks.count(*ti)){ - HIGH_MSG("Skipping track %lu, not in tracks", *ti); + for (std::map::iterator ti = userSelect.begin(); ti != userSelect.end(); ++ti){ + if (ti->first == INVALID_TRACK_ID){ + HIGH_MSG("Skipping track %zu, not in tracks", ti->first); continue; }// ignore missing tracks - DTSC::Track &thisTrack = myMeta.tracks[*ti]; - if (thisTrack.lastms < seekPos + needsLookAhead + extraKeepAway + thisTrack.minKeepAway){ + if (meta.getLastms(ti->first) < seekPos + needsLookAhead + extraKeepAway + mKa){ good = false; break; } - if (mainTrack == *ti){continue;}// skip self - if (thisTrack.lastms == thisTrack.firstms){ - HIGH_MSG("Skipping track %lu, last equals first", *ti); + if (mainTrack == ti->first){continue;}// skip self + if (meta.getLastms(ti->first) == meta.getFirstms(ti->first)){ + HIGH_MSG("Skipping track %lu, last equals first", ti->first); continue; }// ignore point-tracks - HIGH_MSG("Track %lu is good", *ti); + HIGH_MSG("Track %lu is good", ti->first); } // if yes, seek here if (good){ - INFO_MSG("Skipping forward %llums (%u ms LA, %lu ms mKA, %lu eKA, > %lums)", - seekPos - thisPacket.getTime(), needsLookAhead, mainTrk.minKeepAway, extraKeepAway, - seekCount * 250); + HIGH_MSG("Skipping forward %" PRIu64 "ms (%" PRIu64 " ms LA, %" PRIu64 + " ms mKA, %lu eKA, > %" PRIu32 "ms)", + seekPos - cTime, needsLookAhead, mKa, extraKeepAway, seekCount * 100); if (seekCount < 20){++seekCount;} seek(seekPos); return true; @@ -1120,14 +1056,14 @@ namespace Mist{ firstData = false; DONTEVEN_MSG("onRequest"); onRequest(); - lastRecv = Util::epoch(); + lastRecv = Util::bootSecs(); }else{ if (!isBlocking && !parseData){ - if (Util::epoch() - lastRecv > 300){ + if (Util::bootSecs() - lastRecv > 300){ WARN_MSG("Disconnecting 5 minute idle connection"); - myConn.close(); + onFail("Connection idle for 5 minutes"); }else{ - Util::sleep(500); + Util::sleep(20); } } } @@ -1136,7 +1072,7 @@ namespace Mist{ /// Waits for the given amount of millis, increasing the realtime playback /// related times as needed to keep smooth playback intact. void Output::playbackSleep(uint64_t millis){ - if (realTime && myMeta.live){ + if (realTime && M.getLive()){ firstTime += millis; extraKeepAway += millis; } @@ -1193,31 +1129,31 @@ namespace Mist{ if (isFileTarget()){ if (!streamName.size()){ WARN_MSG("Recording unconnected %s output to file! Cancelled.", capa["name"].asString().c_str()); - myConn.close(); + onFail("Unconnected recording output", true); return 2; } initialize(); - if (!myMeta.tracks.size() || !selectedTracks.size() || !keepGoing()){ + if (!M.getValidTracks().size() || !userSelect.size() || !keepGoing()){ INFO_MSG("Stream not available - aborting"); - myConn.close(); + onFail("Stream not available for recording", true); + return 3; + } + if (config->getString("target") == "-"){ + parseData = true; + wantRequest = false; + if (!targetParams.count("realtime")){realTime = 0;} + INFO_MSG("Outputting %s to stdout with %s format", streamName.c_str(), + capa["name"].asString().c_str()); }else{ - if (config->getString("target") == "-"){ - parseData = true; - wantRequest = false; - if (!targetParams.count("realtime")){realTime = 0;} - INFO_MSG("Outputting %s to stdout with %s format", streamName.c_str(), - capa["name"].asString().c_str()); - }else{ - if (connectToFile(config->getString("target"))){ - parseData = true; - wantRequest = false; - if (!targetParams.count("realtime")){realTime = 0;} - INFO_MSG("Recording %s to %s with %s format", streamName.c_str(), - config->getString("target").c_str(), capa["name"].asString().c_str()); - }else{ - myConn.close(); - } + if (!connectToFile(config->getString("target"), targetParams.count("append"))){ + onFail("Could not connect to the target for recording", true); + return 3; } + parseData = true; + wantRequest = false; + if (!targetParams.count("realtime")){realTime = 0;} + INFO_MSG("Recording %s to %s with %s format", streamName.c_str(), + config->getString("target").c_str(), capa["name"].asString().c_str()); } } // Handle CONN_OPEN trigger, if needed @@ -1245,29 +1181,31 @@ namespace Mist{ // slow down processing, if real time speed is wanted if (realTime){ uint8_t i = 6; - while (--i && thisPacket.getTime() > (((Util::getMS() - firstTime) * 1000) + maxSkipAhead) / realTime && + while (--i && thisPacket.getTime() > ((((Util::bootMS() - firstTime) * 1000) + maxSkipAhead) / realTime) && keepGoing()){ - Util::sleep(std::min(thisPacket.getTime() - (((Util::getMS() - firstTime) * 1000) + maxSkipAhead) / realTime, - 1000llu)); + Util::sleep(std::min(thisPacket.getTime() - + ((((Util::bootMS() - firstTime) * 1000) + maxSkipAhead) / realTime), + 1000ul)); stats(); } } // delay the stream until metadata has caught up, if needed if (needsLookAhead){ - // we sleep in 250ms increments, or less if the lookahead time itself is less - uint32_t sleepTime = std::min((uint32_t)250, needsLookAhead); + // we sleep in 20ms increments, or less if the lookahead time itself is less + uint32_t sleepTime = std::min(20ul, needsLookAhead); // wait at most double the look ahead time, plus ten seconds - uint32_t timeoutTries = (needsLookAhead / sleepTime) * 2 + (10000 / sleepTime); + uint64_t timeoutTries = (needsLookAhead / sleepTime) * 2 + (10000 / sleepTime); uint64_t needsTime = thisPacket.getTime() + needsLookAhead; bool firstTime = true; while (--timeoutTries && keepGoing()){ bool lookReady = true; - for (std::set::iterator it = selectedTracks.begin(); - it != selectedTracks.end(); it++){ - if (myMeta.tracks[*it].lastms <= needsTime){ + for (std::map::iterator it = userSelect.begin(); + it != userSelect.end(); it++){ + if (meta.getLastms(it->first) <= needsTime){ if (timeoutTries == 1){ - WARN_MSG("Track %lu: %llu <= %llu", *it, myMeta.tracks[*it].lastms, needsTime); + WARN_MSG("Track %zu: %" PRIu64 " <= %" PRIu64, it->first, + meta.getLastms(it->first), needsTime); } lookReady = false; break; @@ -1280,7 +1218,6 @@ namespace Mist{ playbackSleep(sleepTime); } stats(); - updateMeta(); } if (!timeoutTries){ WARN_MSG("Waiting for lookahead timed out - resetting lookahead!"); @@ -1327,12 +1264,19 @@ namespace Mist{ if (!onFinish()){break;} } } + if (!meta){ + Util::Config::logExitReason("No connection to the metadata"); + break; + } } stats(); } MEDIUM_MSG("MistOut client handler shutting down: %s, %s, %s", myConn.connected() ? "conn_active" : "conn_closed", wantRequest ? "want_request" : "no_want_request", parseData ? "parsing_data" : "not_parsing_data"); + if (Util::Config::exitReason.size()){ + INFO_MSG("Logged exit reason: %s", Util::Config::exitReason.c_str()); + } onFinish(); /*LTS-START*/ @@ -1363,19 +1307,22 @@ namespace Mist{ /*LTS-END*/ stats(true); - nProxy.userClient.finish(); - statsPage.finish(); + if (statComm){statComm.setStatus(COMM_STATUS_DISCONNECT);} + + userSelect.clear(); + myConn.close(); return 0; } - void Output::dropTrack(uint32_t trackId, std::string reason, bool probablyBad){ + void Output::dropTrack(size_t trackId, const std::string &reason, bool probablyBad){ + if (!userSelect.count(trackId)){return;} // depending on whether this is probably bad and the current debug level, print a message - unsigned int printLevel = DLVL_INFO; - if (probablyBad){printLevel = DLVL_WARN;} - DEBUG_MSG(printLevel, "Dropping %s (%s) track %lu@k%lu (nextP=%d, lastP=%d): %s", streamName.c_str(), - myMeta.tracks[trackId].codec.c_str(), (long unsigned)trackId, nxtKeyNum[trackId] + 1, - pageNumForKey(trackId, nxtKeyNum[trackId] + 1), pageNumMax(trackId), reason.c_str()); + size_t printLevel = (probablyBad ? DLVL_WARN : DLVL_INFO); + const Comms::Users &usr = userSelect.at(trackId); + DEBUG_MSG(printLevel, "Dropping %s (%s) track %zu@k%zu (nextP=%zu, lastP=%zu): %s", + streamName.c_str(), meta.getCodec(trackId).c_str(), trackId, usr.getKeyNum() + 1, + pageNumForKey(trackId, usr.getKeyNum() + 1), pageNumMax(trackId), reason.c_str()); // now actually drop the track from the buffer for (std::set::iterator it = buffer.begin(); it != buffer.end(); ++it){ if (it->tid == trackId){ @@ -1383,7 +1330,52 @@ namespace Mist{ break; } } - selectedTracks.erase(trackId); + userSelect.erase(trackId); + } + + /// Assumes at least one video track is selected. + /// Seeks back in the buffer to the newest keyframe with a timestamp less than the current + /// timestamp. Sets thisPacket to that frame, and then undoes the seek. The next call to + /// prepareNext continues as if this function was never called. + bool Output::getKeyFrame(){ + // store copy of current state + std::set tmp_buffer = buffer; + std::map tmp_userSelect = userSelect; + std::map tmp_currentPage = currentPage; + + // reset the current packet to null, assuming failure + thisPacket.null(); + + // find the main track, check if it is video. Abort if not. + size_t mainTrack = getMainSelectedTrack(); + if (M.getType(mainTrack) != "video"){return false;} + + // we now know that mainTrack is a video track - let's do some work! + // first, we remove all selected tracks and the buffer. Then we select only the main track. + uint64_t currTime = currentTime(); + buffer.clear(); + userSelect.clear(); + userSelect[mainTrack].reload(streamName, mainTrack); + // now, seek to the exact timestamp of the keyframe + DTSC::Keys keys(M.keys(mainTrack)); + unsigned int targetKey = keys.getNumForTime(currTime); + seek(keys.getTime(targetKey)); + // attempt to load the key into thisPacket + bool ret = prepareNext(); + if (!ret){ + WARN_MSG("Failed to load keyframe for %" PRIu64 "ms - continuing without it", currTime); + } + + // restore state to before the seek/load + // most of these can simply be copied back... + buffer = tmp_buffer; + userSelect = tmp_userSelect; + // but the currentPage map must also load keys as needed + for (std::map::iterator it = tmp_currentPage.begin(); it != tmp_currentPage.end(); ++it){ + loadPageForKey(it->first, it->second); + } + // now we are back to normal and can return safely + return ret; } /// Attempts to prepare a new packet for output. @@ -1392,12 +1384,10 @@ namespace Mist{ /// \returns true if thisPacket was filled with the next packet. /// \returns false if we could not reliably determine the next packet yet. bool Output::prepareNext(){ - static bool atLivePoint = false; - static int nonVideoCount = 0; - static unsigned int emptyCount = 0; + static size_t emptyCount = 0; if (!buffer.size()){ /// \TODO Do we really need this? Seems error-prone. - if (isRecording() && myMeta.live){ + if (isRecording() && M.getLive()){ selectDefaultTracks(); initialSeek(); return false; @@ -1407,29 +1397,29 @@ namespace Mist{ return true; } // check if we have a next seek point for every track that is selected - if (buffer.size() != selectedTracks.size()){ - std::set dropTracks; - if (buffer.size() < selectedTracks.size()){ + if (buffer.size() != userSelect.size()){ + std::set dropTracks; + if (buffer.size() < userSelect.size()){ // prepare to drop any selectedTrack without buffer entry - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); ++it){ bool found = false; for (std::set::iterator bi = buffer.begin(); bi != buffer.end(); ++bi){ - if (bi->tid == *it){ + if (bi->tid == it->first){ found = true; break; } } - if (!found){dropTracks.insert(*it);} + if (!found){dropTracks.insert(it->first);} } }else{ // prepare to drop any buffer entry without selectedTrack for (std::set::iterator bi = buffer.begin(); bi != buffer.end(); ++bi){ - if (!selectedTracks.count(bi->tid)){dropTracks.insert(bi->tid);} + if (!userSelect.count(bi->tid)){dropTracks.insert(bi->tid);} } } // actually drop what we found. // if both of the above cases occur, the next prepareNext iteration will take care of that - for (std::set::iterator it = dropTracks.begin(); it != dropTracks.end(); ++it){ + for (std::set::iterator it = dropTracks.begin(); it != dropTracks.end(); ++it){ dropTrack(*it, "seek/select mismatch"); } return false; @@ -1437,101 +1427,84 @@ namespace Mist{ sortedPageInfo nxt = *(buffer.begin()); - if (!myMeta.tracks.count(nxt.tid)){ + if (!M.getValidTracks().count(nxt.tid)){ dropTrack(nxt.tid, "disappeared from metadata"); return false; } - DONTEVEN_MSG("Loading track %u (next=%lu), %llu ms, %llub", nxt.tid, nxtKeyNum[nxt.tid], - nxt.time, nxt.offset); - - // if we're going to read past the end of the data page, load the next page - // this only happens for VoD - if (nxt.offset >= nProxy.curPage[nxt.tid].len){ - if (myMeta.vod && nxt.time >= myMeta.tracks[nxt.tid].lastms){ - dropTrack(nxt.tid, "end of VoD track reached", false); - return false; - } - if (thisPacket){nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());} - loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]); + if (M.getPageNumberForTime(nxt.tid, nxt.time) != currentPage[nxt.tid]){ + loadPageForKey(nxt.tid, M.getPageNumberForTime(nxt.tid, nxt.time)); nxt.offset = 0; - if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){ - uint64_t newTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset); - VERYHIGH_MSG("New page %llu for track %llu, first timestamp is %llu ms", nxtKeyNum[nxt.tid], - nxt.tid, newTime); - if (newTime < nxt.time){ - dropTrack(nxt.tid, "time going backwards"); - }else{ - nxt.time = newTime; - // swap out the next object in the buffer with a new one - buffer.erase(buffer.begin()); - buffer.insert(nxt); - } - }else{ - dropTrack(nxt.tid, "VoD page load failure"); - } + nxt.time = getDTSCTime(curPage[nxt.tid].mapped, 0); + buffer.erase(buffer.begin()); + buffer.insert(nxt); return false; } - // have we arrived at the end of the memory page? (4 zeroes mark the end) - if (!memcmp(nProxy.curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4)){ - // if we don't currently know where we are, we're lost. We should drop the track. - if (!nxt.time){ - dropTrack(nxt.tid, "timeless empty packet"); + // if we're going to read past the end of the data page, load the next page + // this only happens for VoD + if (nxt.offset >= curPage[nxt.tid].len || + (!memcmp(curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4))){ + if (M.getVod() && nxt.time >= M.getLastms(nxt.tid)){ + dropTrack(nxt.tid, "end of VoD track reached", false); return false; } - // for VoD, check if we've reached the end of the track, if so, drop it - if (myMeta.vod && nxt.time > myMeta.tracks[nxt.tid].lastms){ - dropTrack(nxt.tid, "Reached end of track", false); - } - // if this is a live stream, we might have just reached the live point. - // check where the next key is - nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, nxt.time); - int nextPage = pageNumForKey(nxt.tid, nxtKeyNum[nxt.tid] + 1); - // if the next key hasn't shown up on another page, then we're waiting. - // VoD might be slow, so we check VoD case also, just in case - if (currKeyOpen.count(nxt.tid) && (currKeyOpen[nxt.tid] == (unsigned int)nextPage || nextPage == -1)){ - if (++emptyCount < 100){ - playbackSleep(250); - // we're waiting for new data to show up - if (emptyCount % 64 == 0){ - reconnect(); // reconnect every 16 seconds - }else{ - // updating meta is only useful with live streams - if (myMeta.live && emptyCount % 4 == 0){updateMeta();} - } - }else{ + dropTrack(nxt.tid, "VoD page load failure"); + return false; + } + + // We know this packet will be valid, pre-load it so we know its length + DTSC::Packet preLoad(curPage[nxt.tid].mapped + nxt.offset, 0, true); + + uint64_t nextTime = 0; + + DTSC::Keys keys(M.keys(nxt.tid)); + size_t thisKey = keys.getNumForTime(nxt.time); + while (!nextTime && keepGoing()){ + // The next packet is either not available yet, or on another page + // Check if we have a next valid packet + if (memcmp(curPage[nxt.tid].mapped + nxt.offset + preLoad.getDataLen(), "\000\000\000\000", 4)){ + nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset + preLoad.getDataLen()); + // After 500ms, we assume the time will not update anymore + if (++emptyCount >= 20){break;} + }else{ + if (M.getVod() && nxt.time >= M.getLastms(nxt.tid)){break;} + if (M.getPageNumberForKey(nxt.tid, thisKey + 1) != currentPage[nxt.tid]){ + // Check if its on a different page + nextTime = keys.getTime(thisKey + 1); + } + if (++emptyCount >= 1000){ // after ~25 seconds, give up and drop the track. dropTrack(nxt.tid, "EOP: data wait timeout"); + return false; + } + Util::sleep(25); + // we're waiting for new data to show up + if (emptyCount % 640 == 0){ + reconnect(); // reconnect every 16 seconds + // if we don't have a connection to the metadata here, this means the stream has gone offline in the meanwhile. + if (!meta){return false;} } - return false; } + } + if (M.getVod() && nxt.time >= M.getLastms(nxt.tid)){ + thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true); + thisIdx = nxt.tid; + dropTrack(nxt.tid, "end of VoD track reached", false); + return true; + } - // The next key showed up on another page! - // We've simply reached the end of the page. Load the next key = next page. - loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]); - nxt.offset = 0; - if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){ - uint64_t nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset); - if (nextTime && nextTime < nxt.time){ - dropTrack(nxt.tid, "EOP: time going backwards (" + JSON::Value(nextTime).asString() + - " < " + JSON::Value(nxt.time).asString() + ")"); - }else{ - if (nextTime){nxt.time = nextTime;} - // swap out the next object in the buffer with a new one - buffer.erase(buffer.begin()); - buffer.insert(nxt); - MEDIUM_MSG("Next page for track %u starts at %llu.", nxt.tid, nxt.time); - } - }else{ - dropTrack(nxt.tid, "next page load failure"); - } + // If we don't have a timestamp at all, this is due to a different cause. + if (!nextTime && (emptyCount < 20)){return false;} + if (nextTime < nxt.time){ + dropTrack(nxt.tid, "time going backwards"); return false; } // we've handled all special cases - at this point the packet should exist // let's load it - thisPacket.reInit(nProxy.curPage[nxt.tid].mapped + nxt.offset, 0, true); + thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true); + thisIdx = nxt.tid; // if it failed, drop the track and continue if (!thisPacket){ dropTrack(nxt.tid, "packet load failure"); @@ -1539,78 +1512,26 @@ namespace Mist{ } emptyCount = 0; // valid packet - reset empty counter - // if there's a timestamp mismatch, print this. - // except for live, where we never know the time in advance - if (thisPacket.getTime() != nxt.time && nxt.time){ - if (!atLivePoint){ - static int warned = 0; - if (warned < 5){ - WARN_MSG("Loaded %s track %ld@%llu instead of %u@%llu (%dms, %s, offset %lu)", - streamName.c_str(), thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, - nxt.time, (int)((long long)thisPacket.getTime() - (long long)nxt.time), - myMeta.tracks[nxt.tid].codec.c_str(), nxt.offset); - if (++warned == 5){ - WARN_MSG("Further warnings about time mismatches printed on HIGH level."); - } - }else{ - HIGH_MSG("Loaded %s track %ld@%llu instead of %u@%llu (%dms, %s, offset %lu)", - streamName.c_str(), thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, - nxt.time, (int)((long long)thisPacket.getTime() - (long long)nxt.time), - myMeta.tracks[nxt.tid].codec.c_str(), nxt.offset); - } - } - nxt.time = thisPacket.getTime(); - // swap out the next object in the buffer with a new one - buffer.erase(buffer.begin()); - buffer.insert(nxt); - VERYHIGH_MSG("JIT reordering %u@%llu.", nxt.tid, nxt.time); + if (!userSelect[nxt.tid].isAlive()){ + INFO_MSG("Track %zu is not alive!", nxt.tid); return false; } - // when live, every keyframe, check correctness of the keyframe number - if (thisPacket.getFlag("keyframe")){ - // cancel if not alive - if (!nProxy.userClient.isAlive()){return false;} - // Check whether returned keyframe is correct. If not, wait for approximately 10 seconds while - // checking. Failure here will cause tracks to drop due to inconsistent internal state. - nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime()); - int counter = 0; - while (myMeta.live && counter < 40 && - myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime() != thisPacket.getTime()){ - if (counter++){ - // Only sleep 250ms if this is not the first updatemeta try - playbackSleep(250); - } - updateMeta(); - nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime()); - } - if (myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime() != thisPacket.getTime()){ - WARN_MSG("Keyframe value is not correct (%llu != %llu) - state will now be inconsistent; " - "resetting", - myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime(), thisPacket.getTime()); - initialSeek(); - return false; - } - EXTREME_MSG("Track %u @ %llums = key %lu", nxt.tid, thisPacket.getTime(), nxtKeyNum[nxt.tid]); - } - - // always assume we're not at the live point - atLivePoint = false; // we assume the next packet is the next on this same page nxt.offset += thisPacket.getDataLen(); - if (nxt.offset < nProxy.curPage[nxt.tid].len){ - unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset); - if (nextTime){ - nxt.time = nextTime; - }else{ - ++nxt.time; - // no packet -> we are at the live point - atLivePoint = true; - } - } + nxt.time = nextTime; + ++nxt.partIndex; + + userSelect[nxt.tid].setKeyNum(thisKey); // exchange the current packet in the buffer for the next one buffer.erase(buffer.begin()); + + if (M.getVod() && nxt.time > M.getLastms(nxt.tid)){ + dropTrack(nxt.tid, "detected last VoD packet"); + return true; + } + buffer.insert(nxt); return true; @@ -1621,11 +1542,8 @@ namespace Mist{ /// The default implementation is usually good enough for all the non-INPUT types. std::string Output::getStatsName(){ if (isPushing()){return "INPUT";} - if (config->hasOption("target") && config->getString("target").size()){ - return "OUTPUT"; - }else{ - return capa["name"].asStringRef(); - } + if (config->hasOption("target") && config->getString("target").size()){return "OUTPUT";} + return capa["name"].asStringRef(); } void Output::stats(bool force){ @@ -1633,86 +1551,60 @@ namespace Mist{ if (!isInitialized){return;} // also cancel if it has been less than a second since the last update // unless force is set to true - unsigned long long int now = Util::epoch(); + uint64_t now = Util::bootSecs(); if (now == lastStats && !force){return;} + + if (!statComm){statComm.reload();} + if (!statComm){return;} + lastStats = now; - HIGH_MSG("Writing stats: %s, %s, %lu, %llu, %llu", getConnectedHost().c_str(), - streamName.c_str(), crc & 0xFFFFFFFFu, myConn.dataUp(), myConn.dataDown()); - if (statsPage.getData()){ - /*LTS-START*/ - if (!statsPage.isAlive()){ - onFail("Shutting down on controller request"); - return; - } - /*LTS-END*/ - IPC::statExchange tmpEx(statsPage.getData()); - tmpEx.now(now); - if (tmpEx.host() == - std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16)){ - tmpEx.host(getConnectedBinHost()); - } - tmpEx.crc(crc); - tmpEx.streamName(streamName); - tmpEx.connector(getStatsName()); - tmpEx.up(myConn.dataUp()); - tmpEx.down(myConn.dataDown()); - tmpEx.time(Util::bootSecs() - myConn.connTime()); - if (thisPacket){ - tmpEx.lastSecond(thisPacket.getTime()); - }else{ - tmpEx.lastSecond(0); - } - /*LTS-START*/ - // Tag the session with the user agent - static bool newUA = true; // we only do this once per connection - if (newUA && ((Util::bootSecs() - myConn.connTime()) >= uaDelay || !myConn) && UA.size()){ - std::string APIcall = - "{\"tag_sessid\":{\"" + tmpEx.getSessId() + "\":" + JSON::string_escape("UA:" + UA) + "}}"; - Socket::UDPConnection uSock; - uSock.SetDestination(UDP_API_HOST, UDP_API_PORT); - uSock.SendNow(APIcall); - newUA = false; - } - /*LTS-END*/ - statsPage.keepAlive(); + HIGH_MSG("Writing stats: %s, %s, %u, %lu, %lu", getConnectedHost().c_str(), streamName.c_str(), + crc & 0xFFFFFFFFu, myConn.dataUp(), myConn.dataDown()); + /*LTS-START*/ + if (statComm.getStatus() == COMM_STATUS_DISCONNECT){ + onFail("Shutting down on controller request"); + return; } + /*LTS-END*/ + statComm.setNow(now); + if (statComm.getHost() == + std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16)){ + statComm.setHost(getConnectedBinHost()); + } + statComm.setCRC(crc); + statComm.setStream(streamName); + statComm.setConnector(getStatsName()); + statComm.setUp(myConn.dataUp()); + statComm.setDown(myConn.dataDown()); + statComm.setTime(now - myConn.connTime()); + statComm.setLastSecond(thisPacket ? thisPacket.getTime() : 0); + statComm.setPid(getpid()); + statComm.keepAlive(); + + /*LTS-START*/ + // Tag the session with the user agent + static bool newUA = true; // we only do this once per connection + if (newUA && ((now - myConn.connTime()) >= uaDelay || !myConn) && UA.size()){ + std::string APIcall = + "{\"tag_sessid\":{\"" + statComm.getSessId() + "\":" + JSON::string_escape("UA:" + UA) + "}}"; + Socket::UDPConnection uSock; + uSock.SetDestination("localhost", 4242); + uSock.SendNow(APIcall); + newUA = false; + } + /*LTS-END*/ + doSync(); - int tNum = 0; - if (!nProxy.userClient.getData()){ - char userPageName[NAME_BUFFER_SIZE]; - snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); - nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); - if (!nProxy.userClient.getData()){ - onFail("Player connection failure - aborting output", true); - return; - } - } - if (!nProxy.userClient.isAlive()){ - if (isPushing() && !pushIsOngoing){ - waitForStreamPushReady(); - if (!nProxy.userClient.isAlive()){ - onFail("Failed to wait for buffer, aborting incoming push", true); - return; + + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + it->second.keepAlive(); + if (it->second.isAlive() && M.getLive() && it->second.getStatus() & COMM_STATUS_SOURCE){ + if (Util::bootSecs() - M.getLastUpdated(it->first) > 3){ + INFO_MSG("Not updating data for track %zu?", it->first); } - }else{ - onFail("Received disconnect request from input"); - return; } } - if (!isPushing()){ - IPC::userConnection userConn(nProxy.userClient.getData()); - for (std::set::iterator it = selectedTracks.begin(); - it != selectedTracks.end() && tNum < SIMUL_TRACKS; it++){ - userConn.setTrackId(tNum, *it); - userConn.setKeynum(tNum, nxtKeyNum[*it]); - tNum++; - } - } - nProxy.userClient.keepAlive(); - if (tNum > SIMUL_TRACKS){ - WARN_MSG("Too many tracks selected, using only first %d", SIMUL_TRACKS); - } } void Output::onRequest(){ @@ -1726,9 +1618,9 @@ namespace Mist{ sentHeader = true; } - bool Output::connectToFile(std::string file){ + bool Output::connectToFile(std::string file, bool append){ int flags = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - int mode = O_RDWR | O_CREAT | O_TRUNC; + int mode = O_RDWR | O_CREAT | (append ? O_APPEND : O_TRUNC); if (!Util::createPathFor(file)){ ERROR_MSG("Cannot not create file %s: could not create parent folder", file.c_str()); return false; @@ -1759,7 +1651,7 @@ namespace Mist{ // Initialize the stream source if needed, connect to it waitForStreamPushReady(); // pull the source setting from metadata - strmSource = myMeta.sourceURI; + strmSource = meta.getSource(); if (!strmSource.size()){ FAIL_MSG("Push rejected - stream %s not configured or unavailable", streamName.c_str()); @@ -1823,6 +1715,7 @@ namespace Mist{ while (streamStatus != STRMSTAT_WAIT && streamStatus != STRMSTAT_READY && keepGoing()){ INFO_MSG("Waiting for %s buffer to be ready... (%u)", streamName.c_str(), streamStatus); disconnect(); + userSelect.clear(); Util::wait(1000); streamStatus = Util::getStreamStatus(streamName); if (streamStatus == STRMSTAT_OFF || streamStatus == STRMSTAT_WAIT || streamStatus == STRMSTAT_READY){ @@ -1831,38 +1724,13 @@ namespace Mist{ streamStatus = Util::getStreamStatus(streamName); } } - if (streamStatus == STRMSTAT_WAIT || streamStatus == STRMSTAT_READY){ - if (!myMeta.sourceURI.size()){ - char pageId[NAME_BUFFER_SIZE]; - snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); - nProxy.metaPages[0].init(pageId, DEFAULT_STRM_PAGE_SIZE); - if (nProxy.metaPages[0].mapped){ - IPC::semaphore *liveSem = 0; - static char liveSemName[NAME_BUFFER_SIZE]; - snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str()); - liveSem = new IPC::semaphore(liveSemName, O_RDWR, ACCESSPERMS, 8, !myMeta.live); - if (*liveSem){ - liveSem->wait(); - }else{ - delete liveSem; - liveSem = 0; - } - DTSC::Packet tmpMeta(nProxy.metaPages[0].mapped, nProxy.metaPages[0].len, true); - if (tmpMeta.getVersion()){ - DTSC::Meta reMeta; - reMeta.reinit(tmpMeta); - myMeta.sourceURI = reMeta.sourceURI; - myMeta.bootMsOffset = reMeta.bootMsOffset; - } - if (liveSem){ - liveSem->post(); - delete liveSem; - liveSem = 0; - } - } - nProxy.metaPages.clear(); - } - } + if (streamStatus == STRMSTAT_READY || streamStatus == STRMSTAT_WAIT){reconnect();} } + void Output::selectAllTracks(){ + std::set tracks = getSupportedTracks(); + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++){ + userSelect[*it].reload(streamName, *it); + } + } }// namespace Mist diff --git a/src/output/output.h b/src/output/output.h index 8fc6477f..51713d5d 100644 --- a/src/output/output.h +++ b/src/output/output.h @@ -2,6 +2,7 @@ #include "../io.h" #include #include +#include #include #include #include @@ -20,9 +21,10 @@ namespace Mist{ if (time < rhs.time){return true;} return (time == rhs.time && tid < rhs.tid); } - uint64_t tid; + size_t tid; uint64_t time; - uint32_t offset; + uint64_t offset; + size_t partIndex; }; /// The output class is intended to be inherited by MistOut process classes. @@ -44,18 +46,17 @@ namespace Mist{ // non-virtual generic functions virtual int run(); virtual void stats(bool force = false); - void seek(unsigned long long pos, bool toKey = false); - bool seek(unsigned int tid, unsigned long long pos, bool getNextKey = false); + void seek(uint64_t pos, bool toKey = false); + bool seek(size_t tid, uint64_t pos, bool getNextKey); + void seekKeyframesIn(unsigned long long pos, unsigned long long maxDelta); void stop(); uint64_t currentTime(); uint64_t startTime(); uint64_t endTime(); uint64_t liveTime(); void setBlocking(bool blocking); - void updateMeta(); - void selectTrack(const std::string &trackType, const std::string &trackVal); /*LTS*/ bool selectDefaultTracks(); - bool connectToFile(std::string file); + bool connectToFile(std::string file, bool append = false); static bool listenMode(){return true;} uint32_t currTrackCount() const; virtual bool isReadyForPlay(); @@ -64,11 +65,13 @@ namespace Mist{ /// This function is called whenever a packet is ready for sending. /// Inside it, thisPacket is guaranteed to contain a valid packet. virtual void sendNext(){}// REQUIRED! Others are optional. + bool getKeyFrame(); bool prepareNext(); - virtual void dropTrack(uint32_t trackId, std::string reason, bool probablyBad = true); + virtual void dropTrack(size_t trackId, const std::string &reason, bool probablyBad = true); virtual void onRequest(); static void listener(Util::Config &conf, int (*callback)(Socket::Connection &S)); virtual void initialSeek(); + uint64_t getMinKeepAway(); virtual bool liveSeek(); virtual bool onFinish(){return false;} void reconnect(); @@ -80,6 +83,8 @@ namespace Mist{ static Util::Config *config; void playbackSleep(uint64_t millis); + void selectAllTracks(); + private: // these *should* not be messed with in child classes. /*LTS-START*/ void Log(std::string type, std::string message); @@ -90,15 +95,16 @@ namespace Mist{ std::string getCountry(std::string ip); void doSync(bool force = false); /*LTS-END*/ - std::map currKeyOpen; - void loadPageForKey(long unsigned int trackId, long long int keyNum); - int pageNumForKey(long unsigned int trackId, long long int keyNum); - int pageNumMax(long unsigned int trackId); + std::map currentPage; + void loadPageForKey(size_t trackId, size_t keyNum); + uint64_t pageNumForKey(size_t trackId, size_t keyNum); + uint64_t pageNumMax(size_t trackId); bool isRecordingToFile; - unsigned int lastStats; ///< Time of last sending of stats. - std::map nxtKeyNum; ///< Contains the number of the next key, for page seeking purposes. + uint64_t lastStats; ///< Time of last sending of stats. + std::set buffer; ///< A sorted list of next-to-be-loaded packets. - bool sought; ///< If a seek has been done, this is set to true. Used for seeking on prepareNext(). + bool sought; ///< If a seek has been done, this is set to true. Used for seeking on + ///< prepareNext(). protected: // these are to be messed with by child classes virtual bool inlineRestartCapable() const{ return false; @@ -106,25 +112,28 @@ namespace Mist{ bool pushing; std::map targetParams; /*LTS*/ std::string UA; ///< User Agent string, if known. - uint16_t uaDelay; ///< Seconds to wait before setting the UA. + uint64_t uaDelay; ///< Seconds to wait before setting the UA. uint64_t lastRecv; uint64_t extraKeepAway; - long long unsigned int firstTime; ///< Time of first packet after last seek. Used for real-time sending. + uint64_t firstTime; ///< Time of first packet after last seek. Used for real-time sending. virtual std::string getConnectedHost(); virtual std::string getConnectedBinHost(); virtual std::string getStatsName(); virtual bool hasSessionIDs(){return false;} - IPC::sharedClient statsPage; ///< Shared memory used for statistics reporting. - bool isBlocking; ///< If true, indicates that myConn is blocking. - uint32_t crc; ///< Checksum, if any, for usage in the stats. - unsigned int getKeyForTime(long unsigned int trackId, long long timeStamp); - uint64_t nextKeyTime(); + std::set getSupportedTracks(const std::string &type = "") const; + + inline bool keepGoing(){return config->is_active && myConn;} + + Comms::Statistics statComm; + bool isBlocking; ///< If true, indicates that myConn is blocking. + uint32_t crc; ///< Checksum, if any, for usage in the stats. // stream delaying variables - unsigned int maxSkipAhead; ///< Maximum ms that we will go ahead of the intended timestamps. - unsigned int realTime; ///< Playback speed in ms of data per second. eg: 0 is infinite, 1000 real-time, 5000 is 0.2X speed, 500 = 2X speed. - uint32_t needsLookAhead; ///< Amount of millis we need to be able to look ahead in the metadata + uint64_t maxSkipAhead; ///< Maximum ms that we will go ahead of the intended timestamps. + uint64_t realTime; ///< Playback speed in ms of wallclock time per data-second. eg: 0 is + ///< infinite, 1000 real-time, 5000 is 0.2X speed, 500 = 2X speed. + uint64_t needsLookAhead; ///< Amount of millis we need to be able to look ahead in the metadata // Read/write status variables Socket::Connection &myConn; ///< Connection to the client. @@ -134,17 +143,17 @@ namespace Mist{ bool isInitialized; ///< If false, triggers initialization if parseData is true. bool sentHeader; ///< If false, triggers sendHeader if parseData is true. - std::map bookKeeping; virtual bool isRecording(); virtual bool isFileTarget(); virtual bool isPushing(){return pushing;}; bool allowPush(const std::string &passwd); void waitForStreamPushReady(); bool pushIsOngoing; - void bufferLivePacket(const DTSC::Packet &packet); + uint64_t firstPacketTime; uint64_t lastPacketTime; - inline bool keepGoing(){return config->is_active && myConn;} + + size_t thisIdx; }; }// namespace Mist diff --git a/src/output/output_cmaf.cpp b/src/output/output_cmaf.cpp new file mode 100644 index 00000000..bbcaccb0 --- /dev/null +++ b/src/output/output_cmaf.cpp @@ -0,0 +1,678 @@ +#include "output_cmaf.h" +#include +#include +#include +#include +#include +#include +#include /*LTS*/ +#include +#include +#include +#include +#include +#include + +namespace Mist{ + + OutCMAF::OutCMAF(Socket::Connection &conn) : HTTPOutput(conn){ + uaDelay = 0; + realTime = 0; + } + + OutCMAF::~OutCMAF(){} + + void OutCMAF::init(Util::Config *cfg){ + HTTPOutput::init(cfg); + capa["name"] = "CMAF"; + capa["friendly"] = "CMAF (fMP4) over HTTP (DASH, HLS7, HSS)"; + capa["desc"] = "Segmented streaming in CMAF (fMP4) format over HTTP"; + capa["url_rel"] = "/cmaf/$/"; + capa["url_prefix"] = "/cmaf/$/"; + capa["socket"] = "http_dash_mp4"; + capa["codecs"][0u][0u].append("+H264"); + capa["codecs"][0u][1u].append("+HEVC"); + capa["codecs"][0u][2u].append("+AAC"); + capa["codecs"][0u][3u].append("+AC3"); + capa["codecs"][0u][4u].append("+MP3"); + capa["codecs"][0u][5u].append("+subtitle"); + capa["encryption"].append("CTR128"); + + capa["methods"][0u]["handler"] = "http"; + capa["methods"][0u]["type"] = "dash/video/mp4"; + capa["methods"][0u]["url_rel"] = "/cmaf/$/index.mpd"; + capa["methods"][0u]["priority"] = 8; + + capa["methods"][1u]["handler"] = "http"; + capa["methods"][1u]["type"] = "html5/application/vnd.apple.mpegurl;version=7"; + capa["methods"][1u]["url_rel"] = "/cmaf/$/index.m3u8"; + capa["methods"][1u]["priority"] = 8; + + capa["methods"][2u]["handler"] = "http"; + capa["methods"][2u]["type"] = "html5/application/vnd.ms-sstr+xml"; + capa["methods"][2u]["url_rel"] = "/cmaf/$/Manifest"; + capa["methods"][2u]["priority"] = 8; + + // MP3 does not work in browsers + capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]"); + + cfg->addOption("nonchunked", + JSON::fromString("{\"short\":\"C\",\"long\":\"nonchunked\",\"help\":\"Do not " + "send chunked, but buffer whole segments.\"}")); + capa["optional"]["nonchunked"]["name"] = "Send whole segments"; + capa["optional"]["nonchunked"]["help"] = + "Disables chunked transfer encoding, forcing per-segment buffering. Reduces performance " + "significantly, but increases compatibility somewhat."; + capa["optional"]["nonchunked"]["option"] = "--nonchunked"; + } + + void OutCMAF::onHTTP(){ + initialize(); + + if (H.url.size() < streamName.length() + 7){ + H.Clean(); + H.SendResponse("404", "Stream not found", myConn); + H.Clean(); + return; + } + + std::string method = H.method; + std::string url = H.url.substr(streamName.length() + 7); // Strip /cmaf// from url + + // Send a dash manifest for any URL with .mpd in the path + if (url.find(".mpd") != std::string::npos){ + sendDashManifest(); + return; + } + + // Send a hls manifest for any URL with index.m3u8 in the path + if (url.find("index.m3u8") != std::string::npos){ + size_t loc = url.find("index.m3u8"); + if (loc == 0){ + sendHlsManifest(); + return; + } + size_t idx = atoll(url.c_str()); + if (url.find("?") == std::string::npos){ + sendHlsManifest(idx); + return; + } + return; + } + + // Send a smooth manifest for any URL with .mpd in the path + if (url.find("Manifest") != std::string::npos){ + sendSmoothManifest(); + return; + } + + H.Clean(); + H.SetHeader("Content-Type", "video/mp4"); + H.SetHeader("Cache-Control", "no-cache"); + H.setCORSHeaders(); + if (method == "OPTIONS" || method == "HEAD"){ + H.SendResponse("200", "OK", myConn); + H.Clean(); + return; + } + + size_t idx = atoll(url.c_str()); + if (url.find("Q(") != std::string::npos){ + idx = atoll(url.c_str() + url.find("Q(") + 2) % 100; + } + if (!M.getValidTracks().count(idx)){ + H.Clean(); + H.SendResponse("404", "Track not found", myConn); + H.Clean(); + return; + } + + if (url.find(".m4s") == std::string::npos){ + H.Clean(); + H.SendResponse("404", "File not found", myConn); + H.Clean(); + return; + } + + // Select the right track + userSelect.clear(); + userSelect[idx].reload(streamName, idx); + + H.StartResponse(H, myConn, config->getBool("nonchunked")); + + if (url.find("init.m4s") != std::string::npos){ + std::string headerData = CMAF::trackHeader(M, idx); + H.Chunkify(headerData.c_str(), headerData.size(), myConn); + H.Chunkify("", 0, myConn); + H.Clean(); + return; + } + + uint64_t startTime = atoll(url.c_str() + url.find("/chunk_") + 7); + if (M.getVod()){startTime += M.getFirstms(idx);} + uint64_t fragmentIndex = M.getFragmentIndexForTime(idx, startTime); + targetTime = M.getTimeForFragmentIndex(idx, fragmentIndex + 1); + + std::string headerData = CMAF::fragmentHeader(M, idx, fragmentIndex); + H.Chunkify(headerData.c_str(), headerData.size(), myConn); + + uint64_t mdatSize = 8 + CMAF::payloadSize(M, idx, fragmentIndex); + char mdatHeader[] ={0x00, 0x00, 0x00, 0x00, 'm', 'd', 'a', 't'}; + Bit::htobl(mdatHeader, mdatSize); + + H.Chunkify(mdatHeader, 8, myConn); + + seek(startTime); + + wantRequest = false; + parseData = true; + } + + void OutCMAF::sendNext(){ + if (thisPacket.getTime() >= targetTime){ + HIGH_MSG("Finished playback to %" PRIu64, targetTime); + wantRequest = true; + parseData = false; + H.Chunkify("", 0, myConn); + H.Clean(); + return; + } + char *data; + size_t dataLen; + thisPacket.getString("data", data, dataLen); + H.Chunkify(data, dataLen, myConn); + } + + /***************************************************************************************************/ + /* Utility */ + /***************************************************************************************************/ + + bool OutCMAF::tracksAligned(const std::set &trackList){ + if (trackList.size() <= 1){return true;} + + size_t baseTrack = *trackList.begin(); + for (std::set::iterator it = trackList.begin(); it != trackList.end(); ++it){ + if (*it == baseTrack){continue;} + if (!M.tracksAlign(*it, baseTrack)){return false;} + } + return true; + } + + void OutCMAF::generateSegmentlist(size_t idx, std::stringstream &s, + void callBack(uint64_t, uint64_t, std::stringstream &, bool)){ + DTSC::Fragments fragments(M.fragments(idx)); + uint32_t firstFragment = fragments.getFirstValid(); + uint32_t endFragment = fragments.getEndValid(); + bool first = true; + // skip the first two fragments if live + if (M.getLive() && (endFragment - firstFragment) > 6){firstFragment += 2;} + + if (M.getType(idx) == "audio"){ + uint32_t mainTrack = M.mainTrack(); + if (mainTrack == INVALID_TRACK_ID){return;} + DTSC::Fragments f(M.fragments(mainTrack)); + uint64_t firstVidTime = M.getTimeForFragmentIndex(mainTrack, f.getFirstValid()); + firstFragment = M.getFragmentIndexForTime(idx, firstVidTime); + } + + DTSC::Keys keys(M.keys(idx)); + for (; firstFragment < endFragment; ++firstFragment){ + uint32_t duration = fragments.getDuration(firstFragment); + uint64_t starttime = keys.getTime(fragments.getFirstKey(firstFragment)); + if (!duration){ + if (M.getLive()){continue;}// skip last fragment when live + duration = M.getLastms(idx) - starttime; + } + if (M.getVod()){starttime -= M.getFirstms(idx);} + callBack(starttime, duration, s, first); + first = false; + } + + /*LTS-START + // remove lines to reduce size towards listlimit setting - but keep at least 4X target + // duration available + uint64_t listlimit = config->getInteger("listlimit"); + if (listlimit){ + while (lines.size() > listlimit && + (totalDuration - durations.front()) > (targetDuration * 4000)){ + lines.pop_front(); + totalDuration -= durations.front(); + durations.pop_front(); + ++skippedLines; + } + } + LTS-END*/ + } + + std::string OutCMAF::buildNalUnit(size_t len, const char *data){ + char *res = (char *)malloc(len + 4); + Bit::htobl(res, len); + memcpy(res + 4, data, len); + return std::string(res, len + 4); + } + + std::string OutCMAF::h264init(const std::string &initData){ + char res[7]; + snprintf(res, 7, "%.2X%.2X%.2X", initData[1], initData[2], initData[3]); + return res; + } + + std::string OutCMAF::h265init(const std::string &initData){ + char res[17]; + snprintf(res, 17, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X", initData[1], initData[6], initData[7], + initData[8], initData[9], initData[10], initData[11], initData[12]); + return res; + } + + /*********************************/ + /* MPEG-DASH Manifest Generation */ + /*********************************/ + + void OutCMAF::sendDashManifest(){ + std::string method = H.method; + H.Clean(); + H.SetHeader("Content-Type", "application/dash+xml"); + H.SetHeader("Cache-Control", "no-cache"); + H.setCORSHeaders(); + if (method == "OPTIONS" || method == "HEAD"){ + H.SendResponse("200", "OK", myConn); + H.Clean(); + return; + } + H.SetBody(dashManifest()); + H.SendResponse("200", "OK", myConn); + H.Clean(); + } + + void dashSegment(uint64_t start, uint64_t duration, std::stringstream &s, bool first){ + s << "" << std::endl; + } + + std::string OutCMAF::dashTime(uint64_t time){ + std::stringstream r; + r << "PT"; + if (time >= 3600000){r << (time / 3600000) << "H";} + if (time >= 60000){r << (time / 60000) % 60 << "M";} + r << (time / 1000) % 60 << "." << std::setfill('0') << std::setw(3) << (time % 1000) << "S"; + return r.str(); + } + + void OutCMAF::dashAdaptationSet(size_t id, size_t idx, std::stringstream &r){ + std::string type = M.getType(idx); + r << "" << std::endl; + } + + void OutCMAF::dashRepresentation(size_t id, size_t idx, std::stringstream &r){ + std::string codec = M.getCodec(idx); + std::string type = M.getType(idx); + r << " " << std::endl; + }else{ + r << "/>"; + } + } + + void OutCMAF::dashSegmentTemplate(std::stringstream &r){ + r << "" + << std::endl; + } + + void OutCMAF::dashAdaptation(size_t id, std::set tracks, bool aligned, std::stringstream &r){ + if (!tracks.size()){return;} + if (aligned){ + size_t firstTrack = *tracks.begin(); + dashAdaptationSet(id, *tracks.begin(), r); + dashSegmentTemplate(r); + generateSegmentlist(firstTrack, r, dashSegment); + r << "" << std::endl; + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++){ + dashRepresentation(id, *it, r); + } + r << "" << std::endl; + return; + } + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++){ + std::string codec = M.getCodec(*it); + std::string type = M.getType(*it); + dashAdaptationSet(id, *tracks.begin(), r); + dashSegmentTemplate(r); + generateSegmentlist(*it, r, dashSegment); + r << "" << std::endl; + dashRepresentation(id, *it, r); + r << "" << std::endl; + } + } + + /// Returns a string with the full XML DASH manifest MPD file. + std::string OutCMAF::dashManifest(bool checkAlignment){ + initialize(); + selectDefaultTracks(); + std::set vTracks; + std::set aTracks; + std::set sTracks; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (M.getType(it->first) == "video"){vTracks.insert(it->first);} + if (M.getType(it->first) == "audio"){aTracks.insert(it->first);} + if (M.getType(it->first) == "subtitle"){sTracks.insert(it->first);} + } + + if (!vTracks.size() && !aTracks.size()){return "";} + + bool videoAligned = checkAlignment && tracksAligned(vTracks); + bool audioAligned = checkAlignment && tracksAligned(aTracks); + + std::stringstream r; + r << "" << std::endl; + r << "" + << std::endl; + r << "" << streamName << "" << std::endl; + r << "" << std::endl; + + dashAdaptation(1, vTracks, videoAligned, r); + dashAdaptation(2, aTracks, audioAligned, r); + + if (sTracks.size()){ + for (std::set::iterator it = sTracks.begin(); it != sTracks.end(); it++){ + std::string lang = (M.getLang(*it) == "" ? "unknown" : M.getLang(*it)); + r << "../../" << streamName + << ".vtt?track=" << *it << "" << std::endl; + } + } + + r << "" << std::endl; + + return r.str(); + } + + /******************************/ + /* HLS v7 Manifest Generation */ + /******************************/ + + void OutCMAF::sendHlsManifest(size_t idx, const std::string &sessId){ + std::string method = H.method; + H.Clean(); + // H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); + H.SetHeader("Content-Type", "audio/mpegurl"); + H.SetHeader("Cache-Control", "no-cache"); + H.setCORSHeaders(); + if (method == "OPTIONS" || method == "HEAD"){ + H.SendResponse("200", "OK", myConn); + H.Clean(); + return; + } + if (idx == INVALID_TRACK_ID){ + H.SetBody(hlsManifest()); + }else{ + H.SetBody(hlsManifest(idx, sessId)); + } + H.SendResponse("200", "OK", myConn); + H.Clean(); + } + + void hlsSegment(uint64_t start, uint64_t duration, std::stringstream &s, bool first){ + s << "#EXTINF:" << (((double)duration) / 1000) << ",\r\nchunk_" << start << ".m4s" << std::endl; + } + + ///\brief Builds an index file for HTTP Live streaming. + ///\return The index file for HTTP Live Streaming. + std::string OutCMAF::hlsManifest(){ + std::stringstream result; + result << "#EXTM3U\r\n#EXT-X-VERSION:7\r\n#EXT-X-INDEPENDENT-SEGMENTS\r\n"; + + selectDefaultTracks(); + std::set vTracks; + std::set aTracks; + std::set sTracks; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (M.getType(it->first) == "video"){vTracks.insert(it->first);} + if (M.getType(it->first) == "audio"){aTracks.insert(it->first);} + if (M.getType(it->first) == "subtitle"){sTracks.insert(it->first);} + } + for (std::set::iterator it = vTracks.begin(); it != vTracks.end(); it++){ + std::string codec = M.getCodec(*it); + if (codec == "H264" || codec == "HEVC" || codec == "MPEG2"){ + int bWidth = M.getBps(*it); + if (bWidth < 5){bWidth = 5;} + if (aTracks.size()){bWidth += M.getBps(*aTracks.begin());} + result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) + << ",RESOLUTION=" << M.getWidth(*it) << "x" << M.getHeight(*it); + if (M.getFpks(*it)){result << ",FRAME-RATE=" << (float)M.getFpks(*it) / 1000;} + if (aTracks.size()){result << ",AUDIO=\"aud1\"";} + if (sTracks.size()){result << ",SUBTITLES=\"sub1\"";} + if (codec == "H264" || codec == "HEVC"){ + result << ",CODECS=\""; + result << Util::codecString(M.getCodec(*it), M.getInit(*it)); + result << "\""; + } + result << "\r\n" << *it; + if (hasSessionIDs()){ + result << "/index.m3u8?sessId=" << getpid() << "\r\n"; + }else{ + result << "/index.m3u8\r\n"; + } + }else if (codec == "subtitle"){ + + if (M.getLang(*it).empty()){meta.setLang(*it, "und");} + + result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << M.getLang(*it) + << "\",NAME=\"" << Encodings::ISO639::decode(M.getLang(*it)) + << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << *it << "/index.m3u8\"" + << "\r\n"; + } + } + for (std::set::iterator it = aTracks.begin(); it != aTracks.end(); it++){ + if (M.getLang(*it).empty()){meta.setLang(*it, "und");} + + result << "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",LANGUAGE=\"" << M.getLang(*it) + << "\",NAME=\"" << Encodings::ISO639::decode(M.getLang(*it)) + << "\",AUTOSELECT=YES,DEFAULT=YES,URI=\"" << *it << "/index.m3u8\"" + << "\r\n"; + } + for (std::set::iterator it = sTracks.begin(); it != sTracks.end(); it++){ + if (M.getLang(*it).empty()){meta.setLang(*it, "und");} + + result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << M.getLang(*it) + << "\",NAME=\"" << Encodings::ISO639::decode(M.getLang(*it)) + << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << *it << "/index.m3u8\"" + << "\r\n"; + } + if (aTracks.size() && !vTracks.size()){ + std::string codec = M.getCodec(*aTracks.begin()); + result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << M.getBps(*aTracks.begin()) * 8; + result << ",CODECS=\"" + << Util::codecString(M.getCodec(*aTracks.begin()), M.getInit(*aTracks.begin())) << "\"\r\n"; + result << *aTracks.begin() << "/index.m3u8\r\n"; + } + HIGH_MSG("Sending this index: %s", result.str().c_str()); + return result.str(); + } + + std::string OutCMAF::hlsManifest(size_t idx, const std::string &sessId){ + std::stringstream result; + // parse single track + uint32_t targetDuration = (M.biggestFragment(idx) / 1000) + 1; + + DTSC::Fragments fragments(M.fragments(idx)); + uint32_t firstFragment = fragments.getFirstValid(); + uint32_t endFragment = fragments.getEndValid(); + // skip the first two fragments if live + if (M.getLive() && (endFragment - firstFragment) > 6){firstFragment += 2;} + if (M.getType(idx) == "audio"){ + uint32_t mainTrack = M.mainTrack(); + if (mainTrack == INVALID_TRACK_ID){return "";} + DTSC::Fragments f(M.fragments(mainTrack)); + uint64_t firstVidTime = M.getTimeForFragmentIndex(mainTrack, f.getFirstValid()); + firstFragment = M.getFragmentIndexForTime(idx, firstVidTime); + } + + result << "#EXTM3U\r\n" + "#EXT-X-VERSION:7\r\n" + "#EXT-X-DISCONTINUITY\r\n" + "#EXT-X-TARGETDURATION:" + << targetDuration << "\r\n"; + if (M.getLive()){result << "#EXT-X-MEDIA-SEQUENCE:" << firstFragment << "\r\n";} + result << "#EXT-X-MAP:URI=\"init.m4s" + << "\"\r\n"; + + generateSegmentlist(idx, result, hlsSegment); + + if (M.getVod()){result << "#EXT-X-ENDLIST\r\n";} + return result.str(); + } + + /****************************************/ + /* Smooth Streaming Manifest Generation */ + /****************************************/ + + std::string toUTF16(const std::string &original){ + std::string result; + result.append("\377\376", 2); + for (std::string::const_iterator it = original.begin(); it != original.end(); it++){ + result += (*it); + result.append("\000", 1); + } + return result; + } + + /// Converts bytes per second and track ID into a single bits per second value, where the last two + /// digits are the track ID. Breaks for track IDs > 99. But really, this is MS-SS, so who cares..? + uint64_t bpsAndIdToBitrate(uint32_t bps, uint64_t tid){ + return ((uint64_t)((bps * 8) / 100)) * 100 + tid; + } + + void smoothSegment(uint64_t start, uint64_t duration, std::stringstream &s, bool first){ + s << "" << std::endl; + } + + void OutCMAF::sendSmoothManifest(){ + std::string method = H.method; + H.Clean(); + H.SetHeader("Content-Type", "application/dash+xml"); + H.SetHeader("Cache-Control", "no-cache"); + H.setCORSHeaders(); + if (method == "OPTIONS" || method == "HEAD"){ + H.SendResponse("200", "OK", myConn); + H.Clean(); + return; + } + H.SetBody(smoothManifest()); + H.SendResponse("200", "OK", myConn); + H.Clean(); + } + + void OutCMAF::smoothAdaptation(const std::string &type, std::set tracks, std::stringstream &r){ + if (!tracks.size()){return;} + DTSC::Keys keys(M.keys(*tracks.begin())); + r << "::iterator it = tracks.begin(); it != tracks.end(); it++){ + size_t width = M.getWidth(*it); + size_t height = M.getHeight(*it); + if (width > maxWidth){maxWidth = width;} + if (height > maxHeight){maxHeight = height;} + } + r << "MaxWidth=\"" << maxWidth << "\" MaxHeight=\"" << maxHeight << "\" DisplayWidth=\"" + << maxWidth << "\" DisplayHeight=\"" << maxHeight << "\""; + } + r << ">\n"; + size_t index = 0; + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++){ + r << "\n"; + } + if (type == "video"){ + MP4::AVCC avccbox; + avccbox.setPayload(M.getInit(*it)); + std::string tmpString = avccbox.asAnnexB(); + for (size_t i = 0; i < tmpString.size(); i++){ + r << std::setfill('0') << std::setw(2) << std::right << (int)tmpString[i]; + } + r << std::dec << "\" MaxWidth=\"" << M.getWidth(*it) << "\" MaxHeight=\"" + << M.getHeight(*it) << "\" FourCC=\"AVC1\" />\n"; + } + } + generateSegmentlist(*tracks.begin(), r, smoothSegment); + r << "\n"; + } + + /// Returns a string with the full XML DASH manifest MPD file. + std::string OutCMAF::smoothManifest(bool checkAlignment){ + initialize(); + + std::stringstream r; + r << "\n" + " vTracks; + std::set aTracks; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (M.getType(it->first) == "video"){vTracks.insert(it->first);} + if (M.getType(it->first) == "audio"){aTracks.insert(it->first);} + } + + if (!aTracks.size() && !vTracks.size()){ + FAIL_MSG("No valid tracks found"); + return ""; + } + + if (M.getVod()){ + r << "Duration=\"" << M.getLastms(vTracks.size() ? *vTracks.begin() : *aTracks.begin()) << "\">\n"; + }else{ + r << "Duration=\"0\" IsLive=\"TRUE\" LookAheadFragmentCount=\"2\" DVRWindowLength=\"" + << M.getBufferWindow() << "\" CanSeek=\"TRUE\" CanPause=\"TRUE\">\n"; + } + + smoothAdaptation("audio", aTracks, r); + smoothAdaptation("video", vTracks, r); + r << "\n"; + + return toUTF16(r.str()); + }// namespace Mist + +}// namespace Mist diff --git a/src/output/output_cmaf.h b/src/output/output_cmaf.h new file mode 100644 index 00000000..76fa63b8 --- /dev/null +++ b/src/output/output_cmaf.h @@ -0,0 +1,43 @@ +#include "output_http.h" +#include +#include + +namespace Mist{ + class OutCMAF : public HTTPOutput{ + public: + OutCMAF(Socket::Connection &conn); + ~OutCMAF(); + static void init(Util::Config *cfg); + void onHTTP(); + void sendNext(); + void sendHeader(){}; + + protected: + void sendDashManifest(); + void dashAdaptationSet(size_t id, size_t idx, std::stringstream &r); + void dashRepresentation(size_t id, size_t idx, std::stringstream &r); + void dashSegmentTemplate(std::stringstream &r); + void dashAdaptation(size_t id, std::set tracks, bool aligned, std::stringstream &r); + std::string dashTime(uint64_t time); + std::string dashManifest(bool checkAlignment = true); + + void sendHlsManifest(size_t idx = INVALID_TRACK_ID, const std::string &sessId = ""); + std::string hlsManifest(); + std::string hlsManifest(size_t idx, const std::string &sessId); + + void sendSmoothManifest(); + std::string smoothManifest(bool checkAlignment = true); + void smoothAdaptation(const std::string &type, std::set tracks, std::stringstream &r); + + void generateSegmentlist(size_t idx, std::stringstream &s, + void callBack(uint64_t, uint64_t, std::stringstream &, bool)); + bool tracksAligned(const std::set &trackList); + std::string buildNalUnit(size_t len, const char *data); + uint64_t targetTime; + + std::string h264init(const std::string &initData); + std::string h265init(const std::string &initData); + }; +}// namespace Mist + +typedef Mist::OutCMAF mistOut; diff --git a/src/output/output_dash_mp4.cpp b/src/output/output_dash_mp4.cpp deleted file mode 100644 index 7e42c807..00000000 --- a/src/output/output_dash_mp4.cpp +++ /dev/null @@ -1,613 +0,0 @@ -#include "output_dash_mp4.h" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Mist{ - OutDashMP4::OutDashMP4(Socket::Connection &conn) : HTTPOutput(conn){ - uaDelay = 0; - realTime = 0; - } - OutDashMP4::~OutDashMP4(){} - - std::string OutDashMP4::makeTime(uint64_t time){ - std::stringstream r; - r << "PT"; - if (time >= 3600000){r << (time / 3600000) << "H";} - if (time >= 60000){r << (time / 60000) % 60 << "M";} - r << (time / 1000) % 60 << "." << std::setfill('0') << std::setw(3) << (time % 1000) << "S"; - return r.str(); - } - - /// Sends an empty moov box for the given track to the connected client, for following up with moof box(es). - void OutDashMP4::sendMoov(uint32_t tid){ - DTSC::Track &Trk = myMeta.tracks[tid]; - - MP4::MOOV moovBox; - MP4::MVHD mvhdBox(0); - mvhdBox.setTrackID(1); - mvhdBox.setDuration(0xFFFFFFFF); - moovBox.setContent(mvhdBox, 0); - - MP4::IODS iodsBox; - if (Trk.type == "video"){ - iodsBox.setODVideoLevel(0xFE); - }else{ - iodsBox.setODAudioLevel(0xFE); - } - moovBox.setContent(iodsBox, 1); - - MP4::MVEX mvexBox; - MP4::MEHD mehdBox; - mehdBox.setFragmentDuration(0xFFFFFFFF); - mvexBox.setContent(mehdBox, 0); - MP4::TREX trexBox; - trexBox.setTrackID(1); - mvexBox.setContent(trexBox, 1); - moovBox.setContent(mvexBox, 2); - - MP4::TRAK trakBox; - MP4::TKHD tkhdBox(1, 0, Trk.width, Trk.height); - tkhdBox.setFlags(3); - if (Trk.type == "audio"){ - tkhdBox.setVolume(256); - tkhdBox.setWidth(0); - tkhdBox.setHeight(0); - } - tkhdBox.setDuration(0xFFFFFFFF); - trakBox.setContent(tkhdBox, 0); - - MP4::MDIA mdiaBox; - MP4::MDHD mdhdBox(0); - mdhdBox.setLanguage(0x44); - mdhdBox.setDuration(Trk.lastms); - mdiaBox.setContent(mdhdBox, 0); - - if (Trk.type == "video"){ - MP4::HDLR hdlrBox(Trk.type, "VideoHandler"); - mdiaBox.setContent(hdlrBox, 1); - }else{ - MP4::HDLR hdlrBox(Trk.type, "SoundHandler"); - mdiaBox.setContent(hdlrBox, 1); - } - - MP4::MINF minfBox; - MP4::DINF dinfBox; - MP4::DREF drefBox; - dinfBox.setContent(drefBox, 0); - minfBox.setContent(dinfBox, 0); - - MP4::STBL stblBox; - MP4::STSD stsdBox; - stsdBox.setVersion(0); - - if (Trk.codec == "H264"){ - MP4::AVC1 avc1Box; - avc1Box.setWidth(Trk.width); - avc1Box.setHeight(Trk.height); - - MP4::AVCC avccBox; - avccBox.setPayload(Trk.init); - avc1Box.setCLAP(avccBox); - stsdBox.setEntry(avc1Box, 0); - } - if (Trk.codec == "HEVC"){ - MP4::HEV1 hev1Box; - hev1Box.setWidth(Trk.width); - hev1Box.setHeight(Trk.height); - - MP4::HVCC hvccBox; - hvccBox.setPayload(Trk.init); - hev1Box.setCLAP(hvccBox); - stsdBox.setEntry(hev1Box, 0); - } - if (Trk.codec == "AAC" || Trk.codec == "MP3"){ - MP4::AudioSampleEntry ase; - ase.setCodec("mp4a"); - ase.setDataReferenceIndex(1); - ase.setSampleRate(Trk.rate); - ase.setChannelCount(Trk.channels); - ase.setSampleSize(Trk.size); - MP4::ESDS esdsBox(Trk.init); - ase.setCodecBox(esdsBox); - stsdBox.setEntry(ase, 0); - } - if (Trk.codec == "AC3"){ - ///\todo Note: this code is copied, note for muxing seperation - MP4::AudioSampleEntry ase; - ase.setCodec("ac-3"); - ase.setDataReferenceIndex(1); - ase.setSampleRate(Trk.rate); - ase.setChannelCount(Trk.channels); - ase.setSampleSize(Trk.size); - MP4::DAC3 dac3Box(Trk.rate, Trk.channels); - ase.setCodecBox(dac3Box); - stsdBox.setEntry(ase, 0); - } - - stblBox.setContent(stsdBox, 0); - - MP4::STTS sttsBox; - sttsBox.setVersion(0); - stblBox.setContent(sttsBox, 1); - - MP4::STSC stscBox; - stscBox.setVersion(0); - stblBox.setContent(stscBox, 2); - - MP4::STCO stcoBox; - stcoBox.setVersion(0); - stblBox.setContent(stcoBox, 3); - - MP4::STSZ stszBox; - stszBox.setVersion(0); - stblBox.setContent(stszBox, 4); - - minfBox.setContent(stblBox, 1); - - if (Trk.type == "video"){ - MP4::VMHD vmhdBox; - vmhdBox.setFlags(1); - minfBox.setContent(vmhdBox, 2); - }else{ - MP4::SMHD smhdBox; - minfBox.setContent(smhdBox, 2); - } - - mdiaBox.setContent(minfBox, 2); - trakBox.setContent(mdiaBox, 1); - moovBox.setContent(trakBox, 3); - - H.Chunkify(moovBox.asBox(), moovBox.boxedSize(), myConn); - } - - void OutDashMP4::sendMoof(uint32_t tid, uint32_t fragIndice){ - DTSC::Track &Trk = myMeta.tracks[tid]; - MP4::MOOF moofBox; - MP4::MFHD mfhdBox; - mfhdBox.setSequenceNumber(fragIndice + Trk.missedFrags); - moofBox.setContent(mfhdBox, 0); - MP4::TRAF trafBox; - MP4::TFHD tfhdBox; - tfhdBox.setTrackID(1); - if (Trk.type == "audio"){ - tfhdBox.setFlags(MP4::tfhdSampleFlag); - tfhdBox.setDefaultSampleFlags(MP4::isKeySample); - } - trafBox.setContent(tfhdBox, 0); - MP4::TFDT tfdtBox; - tfdtBox.setBaseMediaDecodeTime(Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime()); - trafBox.setContent(tfdtBox, 1); - MP4::TRUN trunBox; - - if (Trk.type == "video"){ - uint32_t headSize = 0; - if (Trk.codec == "H264"){ - MP4::AVCC avccBox; - avccBox.setPayload(Trk.init); - headSize = 14 + avccBox.getSPSLen() + avccBox.getPPSLen(); - } - if (Trk.codec == "HEVC"){ - MP4::HVCC hvccBox; - hvccBox.setPayload(myMeta.tracks[tid].init); - std::deque content = hvccBox.getArrays(); - for (std::deque::iterator it = content.begin(); it != content.end(); it++){ - for (std::deque::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){ - headSize += 4 + (*it2).size(); - } - } - } - trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration | - MP4::trunfirstSampleFlags | MP4::trunsampleOffsets); - trunBox.setFirstSampleFlags(MP4::isKeySample); - trunBox.setDataOffset(0); - uint32_t j = 0; - for (DTSC::PartIter parts(Trk, Trk.fragments[fragIndice]); parts; ++parts){ - MP4::trunSampleInformation trunEntry; - trunEntry.sampleSize = parts->getSize(); - if (!j){trunEntry.sampleSize += headSize;} - trunEntry.sampleDuration = parts->getDuration(); - trunEntry.sampleOffset = parts->getOffset(); - trunBox.setSampleInformation(trunEntry, j); - ++j; - } - trunBox.setDataOffset(92 + (12 * j) + 8); - } - if (Trk.type == "audio"){ - trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration); - trunBox.setDataOffset(0); - uint32_t j = 0; - for (DTSC::PartIter parts(Trk, Trk.fragments[fragIndice]); parts; ++parts){ - MP4::trunSampleInformation trunEntry; - trunEntry.sampleSize = parts->getSize(); - trunEntry.sampleDuration = parts->getDuration(); - trunBox.setSampleInformation(trunEntry, j); - ++j; - } - trunBox.setDataOffset(92 + (8 * j) + 8); - } - trafBox.setContent(trunBox, 2); - moofBox.setContent(trafBox, 1); - H.Chunkify(moofBox.asBox(), moofBox.boxedSize(), myConn); - } - - std::string OutDashMP4::buildNalUnit(unsigned int len, const char *data){ - std::stringstream r; - r << (char)((len >> 24) & 0xFF); - r << (char)((len >> 16) & 0xFF); - r << (char)((len >> 8) & 0xFF); - r << (char)((len)&0xFF); - r << std::string(data, len); - return r.str(); - } - - void OutDashMP4::sendMdat(uint32_t tid, uint32_t fragIndice){ - DTSC::Track &Trk = myMeta.tracks[tid]; - DTSC::Fragment &Frag = Trk.fragments[fragIndice]; - uint32_t size = 8 + Frag.getSize(); - if (Trk.codec == "H264"){ - MP4::AVCC avccBox; - avccBox.setPayload(Trk.init); - size += 14 + avccBox.getSPSLen() + avccBox.getPPSLen(); - } - if (Trk.codec == "HEVC"){ - MP4::HVCC hvccBox; - hvccBox.setPayload(Trk.init); - std::deque content = hvccBox.getArrays(); - for (std::deque::iterator it = content.begin(); it != content.end(); it++){ - for (std::deque::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){ - size += 4 + (*it2).size(); - } - } - } - char mdatstr[8] ={0, 0, 0, 0, 'm', 'd', 'a', 't'}; - mdatstr[0] = (char)((size >> 24) & 0xFF); - mdatstr[1] = (char)((size >> 16) & 0xFF); - mdatstr[2] = (char)((size >> 8) & 0xFF); - mdatstr[3] = (char)((size)&0xFF); - H.Chunkify(mdatstr, 8, myConn); - std::string init; - if (Trk.codec == "H264"){ - MP4::AVCC avccBox; - avccBox.setPayload(Trk.init); - init = buildNalUnit(2, "\011\340"); - H.Chunkify(init, myConn); // 09E0 - init = buildNalUnit(avccBox.getSPSLen(), avccBox.getSPS()); - H.Chunkify(init, myConn); - init = buildNalUnit(avccBox.getPPSLen(), avccBox.getPPS()); - H.Chunkify(init, myConn); - } - if (Trk.codec == "HEVC"){ - MP4::HVCC hvccBox; - hvccBox.setPayload(Trk.init); - std::deque content = hvccBox.getArrays(); - for (std::deque::iterator it = content.begin(); it != content.end(); it++){ - for (std::deque::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){ - init = buildNalUnit((*it2).size(), (*it2).c_str()); - H.Chunkify(init, myConn); - } - } - } - // we pull these values first, because seek() destroys our Trk reference - uint64_t startTime = Trk.getKey(Frag.getNumber()).getTime(); - targetTime = startTime + Frag.getDuration(); - HIGH_MSG("Starting playback from %llu to %llu", startTime, targetTime); - wantRequest = false; - parseData = true; - // select only the tid track, and seek to the start time - selectedTracks.clear(); - selectedTracks.insert(tid); - seek(startTime); - } - - void OutDashMP4::sendNext(){ - if (thisPacket.getTime() >= targetTime){ - HIGH_MSG("Finished playback to %llu", targetTime); - wantRequest = true; - parseData = false; - H.Chunkify("", 0, myConn); - H.Clean(); - return; - } - char *data; - size_t dataLen; - thisPacket.getString("data", data, dataLen); - H.Chunkify(data, dataLen, myConn); - } - - /// Examines Trk and adds playable fragments from it to r. - void OutDashMP4::addSegmentTimeline(std::stringstream &r, DTSC::Track &Trk, bool live){ - std::deque::iterator it = Trk.fragments.begin(); - bool first = true; - // skip the first two fragments if live - if (live && Trk.fragments.size() > 6){++(++it);} - for (; it != Trk.fragments.end(); it++){ - uint64_t starttime = Trk.getKey(it->getNumber()).getTime(); - uint32_t duration = it->getDuration(); - if (!duration){ - if (live){continue;}// skip last fragment when live - duration = Trk.lastms - starttime; - } - if (first){ - r << " " << std::endl; - first = false; - }else{ - r << " " << std::endl; - } - } - } - - /// Returns a string with the full XML DASH manifest MPD file. - std::string OutDashMP4::buildManifest(){ - initialize(); - selectDefaultTracks(); - uint64_t lastVidTime = 0; - uint64_t vidInitTrack = 0; - uint64_t lastAudTime = 0; - uint64_t audInitTrack = 0; - uint64_t subInitTrack = 0; - - /// \TODO DASH pretends there is only one audio/video track, and then prints them all using the same timing information. This is obviously wrong if the tracks are not in sync. - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ - if (myMeta.tracks[*it].type == "video" && myMeta.tracks[*it].lastms > lastVidTime){ - lastVidTime = myMeta.tracks[*it].lastms; - vidInitTrack = *it; - } - if (myMeta.tracks[*it].type == "audio" && myMeta.tracks[*it].lastms > lastAudTime){ - lastAudTime = myMeta.tracks[*it].lastms; - audInitTrack = *it; - } - if (myMeta.tracks[*it].codec == "subtitle"){subInitTrack = *it;} - } - std::stringstream r; - - r << "" << std::endl; - r << "" << std::endl; - }else{ - r << "type=\"dynamic\" minimumUpdatePeriod=\"PT2.0S\" availabilityStartTime=\"" - << Util::getUTCString(Util::epoch() - myMeta.tracks[getMainSelectedTrack()].lastms / 1000) << "\" " - << "timeShiftBufferDepth=\"" - << makeTime(myMeta.tracks[getMainSelectedTrack()].lastms - - myMeta.tracks[getMainSelectedTrack()].firstms) - << "\" suggestedPresentationDelay=\"PT5.0S\" minBufferTime=\"PT2.0S\" publishTime=\"" - << Util::getUTCString(Util::epoch()) << "\" >" << std::endl; - } - r << " " << streamName << "" << std::endl; - r << " " << std::endl; - if (vidInitTrack){ - DTSC::Track &trackRef = myMeta.tracks[vidInitTrack]; - r << " " - << std::endl; - r << " " - << std::endl; - r << " " << std::endl; - addSegmentTimeline(r, trackRef, myMeta.live); - r << " " << std::endl; - r << " " << std::endl; - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ - if (myMeta.tracks[*it].codec == "H264"){ - r << " " << std::endl; - } - if (myMeta.tracks[*it].codec == "HEVC"){ - r << " " << std::endl; - } - } - r << " " << std::endl; - } - if (audInitTrack){ - DTSC::Track &trackRef = myMeta.tracks[audInitTrack]; - r << " " - << std::endl; - r << " " << std::endl; - r << " " - << std::endl; - - r << " " << std::endl; - addSegmentTimeline(r, trackRef, myMeta.live); - r << " " << std::endl; - r << " " << std::endl; - - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ - if (myMeta.tracks[*it].codec == "AAC" || myMeta.tracks[*it].codec == "MP3" || - myMeta.tracks[*it].codec == "AC3"){ - r << " " << std::endl; - r << " " << std::endl; - r << " " << std::endl; - } - } - r << " " << std::endl; - } - - if (subInitTrack){ - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ - if (myMeta.tracks[*it].codec == "subtitle"){ - subInitTrack = *it; - std::string lang = (myMeta.tracks[*it].lang == "" ? "unknown" : myMeta.tracks[*it].lang); - r << ""; - r << " "; - r << " ../../" << streamName << ".vtt?track=" << *it << ""; - r << " " << std::endl; - } - } - } - - r << " " << std::endl; - r << "" << std::endl; - - return r.str(); - } - - void OutDashMP4::init(Util::Config *cfg){ - HTTPOutput::init(cfg); - capa["name"] = "DASHMP4"; - capa["friendly"] = "DASH (fMP4) over HTTP"; - capa["desc"] = "Segmented streaming in DASH (fMP4) format over HTTP"; - capa["url_rel"] = "/dash/$/index.mpd"; - capa["url_prefix"] = "/dash/$/"; - capa["socket"] = "http_dash_mp4"; - capa["codecs"][0u][0u].append("+H264"); - capa["codecs"][0u][1u].append("+HEVC"); - capa["codecs"][0u][2u].append("+AAC"); - capa["codecs"][0u][3u].append("+AC3"); - capa["codecs"][0u][4u].append("+MP3"); - capa["codecs"][0u][5u].append("+subtitle"); - - capa["methods"][0u]["handler"] = "http"; - capa["methods"][0u]["type"] = "dash/video/mp4"; - - // MP3 does not work in browsers - capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]"); - // HEVC does not work in browsers - capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]"); - capa["methods"][0u]["priority"] = 8; - - cfg->addOption("nonchunked", - JSON::fromString("{\"short\":\"C\",\"long\":\"nonchunked\",\"help\":\"Do not " - "send chunked, but buffer whole segments.\"}")); - capa["optional"]["nonchunked"]["name"] = "Send whole segments"; - capa["optional"]["nonchunked"]["help"] = - "Disables chunked transfer encoding, forcing per-segment buffering. Reduces performance " - "significantly, but increases compatibility somewhat."; - capa["optional"]["nonchunked"]["option"] = "--nonchunked"; - } - - void OutDashMP4::onHTTP(){ - std::string method = H.method; - - initialize(); - if (myMeta.live){updateMeta();} - std::string url = H.url; - // Send a manifest for any URL with .mpd in the path - if (url.find(".mpd") != std::string::npos){ - H.Clean(); - H.SetHeader("Content-Type", "application/dash+xml"); - H.SetHeader("Cache-Control", "no-cache"); - H.setCORSHeaders(); - if (method == "OPTIONS" || method == "HEAD"){ - H.SendResponse("200", "OK", myConn); - H.Clean(); - return; - } - H.SetBody(buildManifest()); - H.SendResponse("200", "OK", myConn); - H.Clean(); - return; - } - - // Not a manifest - either an init segment or data segment - size_t pos = url.find("chunk_") + 6; // find the track ID position - uint32_t tid = atoi(url.substr(pos).c_str()); - if (!myMeta.tracks.count(tid)){ - H.Clean(); - H.SendResponse("404", "Track not found", myConn); - H.Clean(); - return; - } - H.Clean(); - H.SetHeader("Content-Type", "video/mp4"); - H.SetHeader("Cache-Control", "no-cache"); - H.setCORSHeaders(); - if (method == "OPTIONS" || method == "HEAD"){ - H.SendResponse("200", "OK", myConn); - H.Clean(); - return; - } - H.StartResponse(H, myConn, config->getBool("nonchunked")); - - if (url.find("init.m4s") != std::string::npos){ - // init segment - if (myMeta.tracks[tid].type == "video"){ - H.Chunkify("\000\000\000\040ftypisom\000\000\000\000isomavc1mp42dash", 32, myConn); - }else{ - H.Chunkify("\000\000\000\040ftypisom\000\000\000\000isomM4A mp42dash", 32, myConn); - } - sendMoov(tid); - H.Chunkify("", 0, myConn); - H.Clean(); - return; - } - - // data segment - pos = url.find("_", pos + 1) + 1; - uint64_t timeStamp = atoll(url.substr(pos).c_str()); - uint32_t fragIndice = myMeta.tracks[tid].timeToFragnum(timeStamp); - uint32_t fragNum = myMeta.tracks[tid].fragments[fragIndice].getNumber(); - HIGH_MSG("Getting T%llu for track %lu, indice %lu, number %lu", timeStamp, tid, fragIndice, fragNum); - if (myMeta.live && !myMeta.tracks[tid].fragments[fragIndice].getDuration()){ - size_t ctr = 0; - do{ - if (ctr){Util::sleep(250);} - updateMeta(); - stats(); - }while (!myMeta.tracks[tid].fragments[fragIndice].getDuration() && ++ctr < 120); - if (!myMeta.tracks[tid].fragments[fragIndice].getDuration()){ - WARN_MSG("Sending zero-length segment. This should never happen."); - H.SendResponse("404", "Segment download error", myConn); - H.Clean(); - return; - } - } - DTSC::Track &Trk = myMeta.tracks[tid]; - H.Chunkify("\000\000\000\030stypmsdh\000\000\000\000msdhmsix", 24, myConn); - MP4::SIDX sidxBox; - sidxBox.setReferenceID(1); - sidxBox.setTimescale(1000); - sidxBox.setEarliestPresentationTime(Trk.getKey(fragNum).getTime()); - sidxBox.setFirstOffset(0); - MP4::sidxReference refItem; - refItem.referenceType = false; - if (Trk.fragments[fragIndice].getDuration()){ - refItem.subSegmentDuration = Trk.fragments[fragIndice].getDuration(); - }else{ - refItem.subSegmentDuration = Trk.lastms - Trk.getKey(fragNum).getTime(); - } - refItem.sapStart = false; - refItem.sapType = 0; - refItem.sapDeltaTime = 0; - sidxBox.setReference(refItem, 0); - H.Chunkify(sidxBox.asBox(), sidxBox.boxedSize(), myConn); - sendMoof(tid, fragIndice); - sendMdat(tid, fragIndice); - } - -}// namespace Mist diff --git a/src/output/output_dash_mp4.h b/src/output/output_dash_mp4.h deleted file mode 100644 index 8d6243df..00000000 --- a/src/output/output_dash_mp4.h +++ /dev/null @@ -1,30 +0,0 @@ -#include "output_http.h" -#include -#include - -namespace Mist{ - class OutDashMP4 : public HTTPOutput{ - public: - OutDashMP4(Socket::Connection &conn); - ~OutDashMP4(); - static void init(Util::Config *cfg); - void onHTTP(); - void sendNext(); - void sendHeader(){}; - - protected: - void addSegmentTimeline(std::stringstream &r, DTSC::Track &Trk, bool live); - std::string makeTime(uint64_t time); - std::string buildManifest(); - void sendMoov(uint32_t trackid); - void sendMoof(uint32_t trackid, uint32_t fragIndice); - void sendMdat(uint32_t trackid, uint32_t fragIndice); - std::string buildNalUnit(unsigned int len, const char *data); - uint64_t targetTime; - - std::string h264init(const std::string &initData); - std::string h265init(const std::string &initData); - }; -}// namespace Mist - -typedef Mist::OutDashMP4 mistOut; diff --git a/src/output/output_dtsc.cpp b/src/output/output_dtsc.cpp index efe13c93..7a926ff8 100644 --- a/src/output/output_dtsc.cpp +++ b/src/output/output_dtsc.cpp @@ -37,7 +37,6 @@ namespace Mist{ void OutDTSC::sendCmd(const JSON::Value &data){ MEDIUM_MSG("Sending DTCM: %s", data.toString().c_str()); - unsigned long sendSize = data.packedSize(); myConn.SendNow("DTCM"); char sSize[4] ={0, 0, 0, 0}; Bit::htobl(sSize, data.packedSize()); @@ -64,61 +63,58 @@ namespace Mist{ config = cfg; } - std::string OutDTSC::getStatsName(){ - if (pushing){ - return "INPUT"; - }else{ - return "OUTPUT"; - } - } + std::string OutDTSC::getStatsName(){return (pushing ? "INPUT" : "OUTPUT");} /// Seeks to the first sync'ed keyframe of the main track. /// Aborts if there is no main track or it has no keyframes. void OutDTSC::initialSeek(){ - unsigned long long seekPos = 0; - if (myMeta.live){ - long unsigned int mainTrack = getMainSelectedTrack(); + uint64_t seekPos = 0; + if (M.getLive()){ + size_t mainTrack = getMainSelectedTrack(); // cancel if there are no keys in the main track - if (!myMeta.tracks.count(mainTrack) || !myMeta.tracks[mainTrack].keys.size()){return;} + if (mainTrack == INVALID_TRACK_ID){return;} + + DTSC::Keys keys(M.keys(mainTrack)); + if (!keys.getValidCount()){return;} // seek to the oldest keyframe - for (std::deque::iterator it = myMeta.tracks[mainTrack].keys.begin(); - it != myMeta.tracks[mainTrack].keys.end(); ++it){ - seekPos = it->getTime(); + std::set validTracks = M.getValidTracks(); + for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); ++i){ + seekPos = keys.getTime(i); bool good = true; // check if all tracks have data for this point in time - for (std::set::iterator ti = selectedTracks.begin(); ti != selectedTracks.end(); ++ti){ - if (mainTrack == *ti){continue;}// skip self - if (!myMeta.tracks.count(*ti)){ - HIGH_MSG("Skipping track %lu, not in tracks", *ti); + for (std::map::iterator ti = userSelect.begin(); ti != userSelect.end(); ++ti){ + if (mainTrack == ti->first){continue;}// skip self + if (!validTracks.count(ti->first)){ + HIGH_MSG("Skipping track %zu, not in tracks", ti->first); continue; }// ignore missing tracks - if (myMeta.tracks[*ti].lastms == myMeta.tracks[*ti].firstms){ - HIGH_MSG("Skipping track %lu, last equals first", *ti); + if (M.getLastms(ti->first) == M.getFirstms(ti->first)){ + HIGH_MSG("Skipping track %zu, last equals first", ti->first); continue; }// ignore point-tracks - if (myMeta.tracks[*ti].firstms > seekPos){ + if (M.getFirstms(ti->first) > seekPos){ good = false; break; } - HIGH_MSG("Track %lu is good", *ti); + HIGH_MSG("Track %zu is good", ti->first); } // if yes, seek here if (good){break;} } } - MEDIUM_MSG("Initial seek to %llums", seekPos); + MEDIUM_MSG("Initial seek to %" PRIu64 "ms", seekPos); seek(seekPos); } void OutDTSC::sendNext(){ // If there are now more selectable tracks, select the new track and do a seek to the current // timestamp Set sentHeader to false to force it to send init data - if (selectedTracks.size() < 2){ - static unsigned long long lastMeta = 0; + if (userSelect.size() < 2){ + static uint64_t lastMeta = 0; if (Util::epoch() > lastMeta + 5){ lastMeta = Util::epoch(); - updateMeta(); - if (myMeta.tracks.size() > 1){ + std::set validTracks = getSupportedTracks(); + if (validTracks.size() > 1){ if (selectDefaultTracks()){ INFO_MSG("Track selection changed - resending headers and continuing"); sentHeader = false; @@ -127,21 +123,24 @@ namespace Mist{ } } } - myConn.SendNow(thisPacket.getData(), thisPacket.getDataLen()); + DTSC::Packet p(thisPacket, thisIdx + 1); + myConn.SendNow(p.getData(), p.getDataLen()); lastActive = Util::epoch(); } void OutDTSC::sendHeader(){ sentHeader = true; - selectedTracks.clear(); - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (it->second.type == "video" || it->second.type == "audio"){ - selectedTracks.insert(it->first); + userSelect.clear(); + std::set validTracks = M.getValidTracks(); + std::set selectedTracks; + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (M.getType(*it) == "video" || M.getType(*it) == "audio"){ + userSelect[*it].reload(streamName, *it); + selectedTracks.insert(*it); } } - myMeta.send(myConn, true, selectedTracks); - if (myMeta.live){realTime = 0;} + M.send(myConn, true, selectedTracks, true); + if (M.getLive()){realTime = 0;} } void OutDTSC::onFail(const std::string &msg, bool critical){ @@ -184,7 +183,7 @@ namespace Mist{ continue; } if (dScan.getMember("cmd").asString() == "reset"){ - myMeta.reset(); + meta.reInit(streamName); sendOk("Internal state reset"); continue; } @@ -200,9 +199,9 @@ namespace Mist{ if (!myConn.Received().available(8 + rSize)){return;}// abort - not enough data yet std::string dataPacket = myConn.Received().remove(8 + rSize); DTSC::Packet metaPack(dataPacket.data(), dataPacket.size()); - myMeta.reinit(metaPack); + meta.reInit(streamName, metaPack.getScan()); std::stringstream rep; - rep << "DTSC_HEAD received with " << myMeta.tracks.size() << " tracks. Bring on those data packets!"; + rep << "DTSC_HEAD received with " << M.getValidTracks().size() << " tracks. Bring on those data packets!"; sendOk(rep.str()); }else if (myConn.Received().copy(4) == "DTP2"){ if (!isPushing()){ @@ -215,7 +214,7 @@ namespace Mist{ if (!myConn.Received().available(8 + rSize)){return;}// abort - not enough data yet std::string dataPacket = myConn.Received().remove(8 + rSize); DTSC::Packet inPack(dataPacket.data(), dataPacket.size(), true); - if (!myMeta.tracks.count(inPack.getTrackId())){ + if (M.trackIDToIndex(inPack.getTrackId(), getpid()) == INVALID_TRACK_ID){ onFail("DTSC_V2 received for a track that was not announced in the DTSC_HEAD!", true); return; } diff --git a/src/output/output_dtsc.h b/src/output/output_dtsc.h index d1b73f7c..56b8b5ff 100644 --- a/src/output/output_dtsc.h +++ b/src/output/output_dtsc.h @@ -22,7 +22,6 @@ namespace Mist{ std::string salt; void handlePush(DTSC::Scan &dScan); void handlePlay(DTSC::Scan &dScan); - unsigned long long fastAsPossibleTime; }; }// namespace Mist diff --git a/src/output/output_ebml.cpp b/src/output/output_ebml.cpp index dd1f4867..b88b68d3 100644 --- a/src/output/output_ebml.cpp +++ b/src/output/output_ebml.cpp @@ -16,7 +16,7 @@ namespace Mist{ if (config->getString("target").size()){ if (config->getString("target").find(".webm") != std::string::npos){doctype = "webm";} initialize(); - if (myMeta.vod){calcVodSizes();} + if (M.getVod()){calcVodSizes();} if (!streamName.size()){ WARN_MSG("Recording unconnected EBML output to file! Cancelled."); conn.close(); @@ -28,7 +28,7 @@ namespace Mist{ INFO_MSG("Outputting %s to stdout in EBML format", streamName.c_str()); return; } - if (!myMeta.tracks.size()){ + if (!M.getValidTracks().size()){ INFO_MSG("Stream not available - aborting"); conn.close(); return; @@ -106,28 +106,31 @@ namespace Mist{ bool OutEBML::isRecording(){return config->getString("target").size();} /// Calculates the size of a Cluster (contents only) and returns it. - /// Bases the calculation on the currently selected tracks and the given start/end time for the cluster. - uint32_t OutEBML::clusterSize(uint64_t start, uint64_t end){ - uint32_t sendLen = EBML::sizeElemUInt(EBML::EID_TIMECODE, start); - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - DTSC::Track &thisTrack = myMeta.tracks[*it]; - uint32_t firstPart = 0; + /// Bases the calculation on the currently selected tracks and the given start/end time for the + /// cluster. + size_t OutEBML::clusterSize(uint64_t start, uint64_t end){ + size_t sendLen = EBML::sizeElemUInt(EBML::EID_TIMECODE, start); + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + DTSC::Keys keys(M.keys(it->first)); + DTSC::Parts parts(M.parts(it->first)); + + uint32_t firstPart = parts.getFirstValid(); unsigned long long int prevParts = 0; uint64_t curMS = 0; - for (std::deque::iterator it2 = thisTrack.keys.begin(); it2 != thisTrack.keys.end(); it2++){ - if (it2->getTime() > start && it2 != thisTrack.keys.begin()){break;} + + for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); ++i){ + if (keys.getTime(i) > start && i != keys.getFirstValid()){break;} firstPart += prevParts; - prevParts = it2->getParts(); - curMS = it2->getTime(); + prevParts = keys.getParts(i); + curMS = keys.getTime(i); } - size_t maxParts = thisTrack.parts.size(); - for (size_t i = firstPart; i < maxParts; i++){ + for (size_t i = firstPart; i < parts.getEndValid(); ++i){ if (curMS >= end){break;} if (curMS >= start){ - uint32_t blkLen = EBML::sizeSimpleBlock(thisTrack.trackID, thisTrack.parts[i].getSize()); + uint32_t blkLen = EBML::sizeSimpleBlock(it->first + 1, parts.getSize(i)); sendLen += blkLen; } - curMS += thisTrack.parts[i].getDuration(); + curMS += parts.getDuration(i); } } return sendLen; @@ -137,19 +140,21 @@ namespace Mist{ if (thisPacket.getTime() >= newClusterTime){ if (liveSeek()){return;} currentClusterTime = thisPacket.getTime(); - if (myMeta.vod){ + if (M.getVod()){ // In case of VoD, clusters are aligned with the main track fragments - // EXCEPT when they are more than 30 seconds long, because clusters are limited to -32 to 32 seconds. - DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; - uint32_t fragIndice = Trk.timeToFragnum(currentClusterTime); - newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() + - Trk.fragments[fragIndice].getDuration(); + // EXCEPT when they are more than 30 seconds long, because clusters are limited to -32 to 32 + // seconds. + size_t idx = getMainSelectedTrack(); + DTSC::Fragments fragments(M.fragments(idx)); + uint32_t fragIndice = M.getFragmentIndexForTime(idx, currentClusterTime); + newClusterTime = M.getTimeForFragmentIndex(idx, fragIndice) + fragments.getDuration(fragIndice); // Limit clusters to 30s, and the last fragment should always be 30s, just in case. - if ((newClusterTime - currentClusterTime > 30000) || (fragIndice == Trk.fragments.size() - 1)){ + if ((newClusterTime - currentClusterTime > 30000) || (fragIndice == fragments.getEndValid() - 1)){ newClusterTime = currentClusterTime + 30000; } - EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, - fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime)); + EXTREME_MSG("Cluster: %" PRIu64 " - %" PRIu64 " (%" PRIu32 "/%zu) = %zu", + currentClusterTime, newClusterTime, fragIndice, fragments.getEndValid(), + clusterSize(currentClusterTime, newClusterTime)); }else{ // In live, clusters are aligned with the lookAhead time newClusterTime = currentClusterTime + (needsLookAhead ? needsLookAhead : 1); @@ -162,152 +167,167 @@ namespace Mist{ EBML::sendElemUInt(myConn, EBML::EID_TIMECODE, currentClusterTime); } - EBML::sendSimpleBlock(myConn, thisPacket, currentClusterTime, - myMeta.tracks[thisPacket.getTrackId()].type != "video"); + DTSC::Packet p(thisPacket, thisIdx + 1); + EBML::sendSimpleBlock(myConn, p, currentClusterTime, M.getType(thisIdx) != "video"); } - std::string OutEBML::trackCodecID(const DTSC::Track &Trk){ - if (Trk.codec == "opus"){return "A_OPUS";} - if (Trk.codec == "H264"){return "V_MPEG4/ISO/AVC";} - if (Trk.codec == "HEVC"){return "V_MPEGH/ISO/HEVC";} - if (Trk.codec == "VP8"){return "V_VP8";} - if (Trk.codec == "VP9"){return "V_VP9";} - if (Trk.codec == "AV1"){return "V_AV1";} - if (Trk.codec == "AAC"){return "A_AAC";} - if (Trk.codec == "vorbis"){return "A_VORBIS";} - if (Trk.codec == "theora"){return "V_THEORA";} - if (Trk.codec == "MPEG2"){return "V_MPEG2";} - if (Trk.codec == "PCM"){return "A_PCM/INT/BIG";} - if (Trk.codec == "MP2"){return "A_MPEG/L2";} - if (Trk.codec == "MP3"){return "A_MPEG/L3";} - if (Trk.codec == "AC3"){return "A_AC3";} - if (Trk.codec == "ALAW"){return "A_MS/ACM";} - if (Trk.codec == "ULAW"){return "A_MS/ACM";} - if (Trk.codec == "FLOAT"){return "A_PCM/FLOAT/IEEE";} - if (Trk.codec == "DTS"){return "A_DTS";} - if (Trk.codec == "JSON"){return "M_JSON";} + std::string OutEBML::trackCodecID(size_t idx){ + std::string codec = M.getCodec(idx); + if (codec == "opus"){return "A_OPUS";} + if (codec == "H264"){return "V_MPEG4/ISO/AVC";} + if (codec == "HEVC"){return "V_MPEGH/ISO/HEVC";} + if (codec == "VP8"){return "V_VP8";} + if (codec == "VP9"){return "V_VP9";} + if (codec == "AV1"){return "V_AV1";} + if (codec == "AAC"){return "A_AAC";} + if (codec == "vorbis"){return "A_VORBIS";} + if (codec == "theora"){return "V_THEORA";} + if (codec == "MPEG2"){return "V_MPEG2";} + if (codec == "PCM"){return "A_PCM/INT/BIG";} + if (codec == "MP2"){return "A_MPEG/L2";} + if (codec == "MP3"){return "A_MPEG/L3";} + if (codec == "AC3"){return "A_AC3";} + if (codec == "ALAW"){return "A_MS/ACM";} + if (codec == "ULAW"){return "A_MS/ACM";} + if (codec == "FLOAT"){return "A_PCM/FLOAT/IEEE";} + if (codec == "DTS"){return "A_DTS";} + if (codec == "JSON"){return "M_JSON";} return "E_UNKNOWN"; } - void OutEBML::sendElemTrackEntry(const DTSC::Track &Trk){ + void OutEBML::sendElemTrackEntry(size_t idx){ // First calculate the sizes of the TrackEntry and Audio/Video elements. - uint32_t sendLen = 0; - uint32_t subLen = 0; - sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, Trk.trackID); - sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, Trk.trackID); - sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(Trk)); - sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und"); + size_t sendLen = 0; + size_t subLen = 0; + sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, idx + 1); + sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, idx + 1); + sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(idx)); + sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, M.getLang(idx).size() ? M.getLang(idx) : "und"); sendLen += EBML::sizeElemUInt(EBML::EID_FLAGLACING, 0); - if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){ + std::string codec = M.getCodec(idx); + if (codec == "ALAW" || codec == "ULAW"){ sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, std::string((size_t)18, '\000')); }else{ - if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);} + if (M.getInit(idx).size()){ + sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, M.getInit(idx)); + } } - if (Trk.codec == "opus" && Trk.init.size() > 11){ - sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data()) * 1000000 / 48); + if (codec == "opus" && M.getInit(idx).size() > 11){ + sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(M.getInit(idx).data()) * 1000000 / 48); sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000); } - if (Trk.type == "video"){ + std::string type = M.getType(idx); + if (type == "video"){ sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1); - subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width); - subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height); - subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width); - subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height); + subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, M.getWidth(idx)); + subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, M.getHeight(idx)); + subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, M.getWidth(idx)); + subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, M.getHeight(idx)); sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen); } - if (Trk.type == "audio"){ + if (type == "audio"){ sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 2); - subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, Trk.channels); - subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, Trk.rate); - subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size); + subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, M.getChannels(idx)); + subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, M.getRate(idx)); + subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, M.getSize(idx)); sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen); } - if (Trk.type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);} + if (type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);} sendLen += subLen; // Now actually send. EBML::sendElemHead(myConn, EBML::EID_TRACKENTRY, sendLen); - EBML::sendElemUInt(myConn, EBML::EID_TRACKNUMBER, Trk.trackID); - EBML::sendElemUInt(myConn, EBML::EID_TRACKUID, Trk.trackID); - EBML::sendElemStr(myConn, EBML::EID_CODECID, trackCodecID(Trk)); - EBML::sendElemStr(myConn, EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und"); + EBML::sendElemUInt(myConn, EBML::EID_TRACKNUMBER, idx + 1); + EBML::sendElemUInt(myConn, EBML::EID_TRACKUID, idx + 1); + EBML::sendElemStr(myConn, EBML::EID_CODECID, trackCodecID(idx)); + EBML::sendElemStr(myConn, EBML::EID_LANGUAGE, M.getLang(idx).size() ? M.getLang(idx) : "und"); EBML::sendElemUInt(myConn, EBML::EID_FLAGLACING, 0); - if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){ - std::string init = RIFF::fmt::generate(((Trk.codec == "ALAW") ? 6 : 7), Trk.channels, Trk.rate, - Trk.bps, Trk.channels * (Trk.size << 3), Trk.size); + if (codec == "ALAW" || codec == "ULAW"){ + std::string init = + RIFF::fmt::generate(((codec == "ALAW") ? 6 : 7), M.getChannels(idx), M.getRate(idx), + M.getBps(idx), M.getChannels(idx) * (M.getSize(idx) << 3), M.getSize(idx)); EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, init.substr(8)); }else{ - if (Trk.init.size()){EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, Trk.init);} + if (M.getInit(idx).size()){ + EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, M.getInit(idx)); + } } - if (Trk.codec == "opus"){ - EBML::sendElemUInt(myConn, EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data()) * 1000000 / 48); + if (codec == "opus" && M.getInit(idx).size() > 11){ + EBML::sendElemUInt(myConn, EBML::EID_CODECDELAY, Opus::getPreSkip(M.getInit(idx).data()) * 1000000 / 48); EBML::sendElemUInt(myConn, EBML::EID_SEEKPREROLL, 80000000); } - if (Trk.type == "video"){ + if (type == "video"){ EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 1); EBML::sendElemHead(myConn, EBML::EID_VIDEO, subLen); - EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, Trk.width); - EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, Trk.height); - EBML::sendElemUInt(myConn, EBML::EID_DISPLAYWIDTH, Trk.width); - EBML::sendElemUInt(myConn, EBML::EID_DISPLAYHEIGHT, Trk.height); + EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, M.getWidth(idx)); + EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, M.getHeight(idx)); + EBML::sendElemUInt(myConn, EBML::EID_DISPLAYWIDTH, M.getWidth(idx)); + EBML::sendElemUInt(myConn, EBML::EID_DISPLAYHEIGHT, M.getHeight(idx)); } - if (Trk.type == "audio"){ + if (type == "audio"){ EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 2); EBML::sendElemHead(myConn, EBML::EID_AUDIO, subLen); - EBML::sendElemUInt(myConn, EBML::EID_CHANNELS, Trk.channels); - EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, Trk.rate); - EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, Trk.size); + EBML::sendElemUInt(myConn, EBML::EID_CHANNELS, M.getChannels(idx)); + EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, M.getRate(idx)); + EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, M.getSize(idx)); } - if (Trk.type == "meta"){EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 3);} + if (type == "meta"){EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 3);} } - uint32_t OutEBML::sizeElemTrackEntry(const DTSC::Track &Trk){ + size_t OutEBML::sizeElemTrackEntry(size_t idx){ // Calculate the sizes of the TrackEntry and Audio/Video elements. - uint32_t sendLen = 0; - uint32_t subLen = 0; - sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, Trk.trackID); - sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, Trk.trackID); - sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(Trk)); - sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und"); + size_t sendLen = 0; + size_t subLen = 0; + sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, idx + 1); + sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, idx + 1); + sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(idx)); + sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, M.getLang(idx).size() ? M.getLang(idx) : "und"); sendLen += EBML::sizeElemUInt(EBML::EID_FLAGLACING, 0); - if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){ + std::string codec = M.getCodec(idx); + if (codec == "ALAW" || codec == "ULAW"){ sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, std::string((size_t)18, '\000')); }else{ - if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);} + if (M.getInit(idx).size()){ + sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, M.getInit(idx)); + } } - if (Trk.codec == "opus"){ - sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data()) * 1000000 / 48); + std::string type = M.getType(idx); + if (codec == "opus" && M.getInit(idx).size() > 11){ + sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(M.getInit(idx).data()) * 1000000 / 48); sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000); } - if (Trk.type == "video"){ + if (type == "video"){ sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1); - subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width); - subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height); - subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width); - subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height); + subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, M.getWidth(idx)); + subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, M.getHeight(idx)); + subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, M.getWidth(idx)); + subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, M.getHeight(idx)); sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen); } - if (Trk.type == "audio"){ + if (type == "audio"){ sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 2); - subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, Trk.channels); - subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, Trk.rate); - subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size); + subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, M.getChannels(idx)); + subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, M.getRate(idx)); + subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, M.getSize(idx)); sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen); } - if (Trk.type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);} + if (type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);} sendLen += subLen; return EBML::sizeElemHead(EBML::EID_TRACKENTRY, sendLen) + sendLen; } void OutEBML::sendHeader(){ double duration = 0; - DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; - if (myMeta.vod){duration = Trk.lastms - Trk.firstms;} - if (myMeta.live){needsLookAhead = 420;} + size_t idx = getMainSelectedTrack(); + if (M.getVod()){ + duration = M.getLastms(idx) - M.getFirstms(idx); + }else{ + needsLookAhead = 420; + } // EBML header and Segment EBML::sendElemEBML(myConn, doctype); EBML::sendElemHead(myConn, EBML::EID_SEGMENT, segmentSize); // Default = Unknown size - if (myMeta.vod){ + if (M.getVod()){ // SeekHead EBML::sendElemHead(myConn, EBML::EID_SEEKHEAD, seekSize); EBML::sendElemSeek(myConn, EBML::EID_INFO, seekheadSize); @@ -317,38 +337,39 @@ namespace Mist{ // Info EBML::sendElemInfo(myConn, "MistServer " PACKAGE_VERSION, duration); // Tracks - uint32_t trackSizes = 0; - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - trackSizes += sizeElemTrackEntry(myMeta.tracks[*it]); + size_t trackSizes = 0; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + trackSizes += sizeElemTrackEntry(it->first); } EBML::sendElemHead(myConn, EBML::EID_TRACKS, trackSizes); - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - sendElemTrackEntry(myMeta.tracks[*it]); + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + sendElemTrackEntry(it->first); } - if (myMeta.vod){ + if (M.getVod()){ EBML::sendElemHead(myConn, EBML::EID_CUES, cuesSize); uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize); for (std::map::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ - EBML::sendElemCuePoint(myConn, it->first, Trk.trackID, tmpsegSize, 0); + EBML::sendElemCuePoint(myConn, it->first, idx + 1, tmpsegSize, 0); tmpsegSize += it->second; } } sentHeader = true; } - /// Seeks to the given byte position by doing a regular seek and remembering the byte offset from that point + /// Seeks to the given byte position by doing a regular seek and remembering the byte offset from + /// that point void OutEBML::byteSeek(uint64_t startPos){ - INFO_MSG("Seeking to %llu bytes", startPos); + INFO_MSG("Seeking to %" PRIu64 " bytes", startPos); sentHeader = false; newClusterTime = 0; if (startPos == 0){ seek(0); return; } - uint64_t headerSize = EBML::sizeElemEBML(doctype) + - EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + seekheadSize + infoSize + - tracksSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize) + cuesSize; + size_t headerSize = EBML::sizeElemEBML(doctype) + + EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + seekheadSize + infoSize + + tracksSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize) + cuesSize; if (startPos < headerSize){ HIGH_MSG("Seek went into or before header"); seek(0); @@ -357,11 +378,10 @@ namespace Mist{ } startPos -= headerSize; sentHeader = true; // skip the header - DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; for (std::map::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ - VERYHIGH_MSG("Cluster %llu (%llu bytes) -> %llu to go", it->first, it->second, startPos); + VERYHIGH_MSG("Cluster %" PRIu64 " (%" PRIu64 " bytes) -> %" PRIu64 " to go", it->first, it->second, startPos); if (startPos < it->second){ - HIGH_MSG("Seek to fragment at %llu ms", it->first); + HIGH_MSG("Seek to fragment at %" PRIu64 " ms", it->first); myConn.skipBytes(startPos); seek(it->first); newClusterTime = it->first; @@ -389,15 +409,15 @@ namespace Mist{ } // Calculate the sizes of various parts, if we're VoD. - uint64_t totalSize = 0; - if (myMeta.vod){ + size_t totalSize = 0; + if (M.getVod()){ calcVodSizes(); // We now know the full size of the segment, thus can calculate the total size totalSize = EBML::sizeElemEBML(doctype) + EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + segmentSize; } - uint64_t byteEnd = totalSize - 1; - uint64_t byteStart = 0; + size_t byteEnd = totalSize - 1; + size_t byteStart = 0; /*LTS-START*/ // allow setting of max lead time through buffer variable. @@ -424,12 +444,12 @@ namespace Mist{ /*LTS-END*/ char rangeType = ' '; - if (!myMeta.live){ + if (M.getVod()){ if (H.GetHeader("Range") != ""){ if (parseRange(byteStart, byteEnd)){ if (H.GetVar("buffer") == ""){ - DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; - maxSkipAhead = (Trk.lastms - Trk.firstms) / 20 + 7500; + size_t idx = getMainSelectedTrack(); + maxSkipAhead = (M.getLastms(idx) - M.getFirstms(idx)) / 20 + 7500; } } rangeType = H.GetHeader("Range")[0]; @@ -438,33 +458,29 @@ namespace Mist{ H.Clean(); // make sure no parts of old requests are left in any buffers H.setCORSHeaders(); H.SetHeader("Content-Type", "video/webm"); - if (myMeta.vod){H.SetHeader("Accept-Ranges", "bytes, parsec");} + if (M.getVod()){H.SetHeader("Accept-Ranges", "bytes, parsec");} if (rangeType != ' '){ if (!byteEnd){ if (rangeType == 'p'){ H.SetBody("Starsystem not in communications range"); H.SendResponse("416", "Starsystem not in communications range", myConn); return; - }else{ - H.SetBody("Requested Range Not Satisfiable"); - H.SendResponse("416", "Requested Range Not Satisfiable", myConn); - return; } - }else{ - std::stringstream rangeReply; - rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << totalSize; - H.SetHeader("Content-Length", byteEnd - byteStart + 1); - H.SetHeader("Content-Range", rangeReply.str()); - /// \todo Switch to chunked? - H.SendResponse("206", "Partial content", myConn); - // H.StartResponse("206", "Partial content", HTTP_R, conn); - byteSeek(byteStart); + H.SetBody("Requested Range Not Satisfiable"); + H.SendResponse("416", "Requested Range Not Satisfiable", myConn); + return; } + std::stringstream rangeReply; + rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << totalSize; + H.SetHeader("Content-Length", byteEnd - byteStart + 1); + H.SetHeader("Content-Range", rangeReply.str()); + /// \todo Switch to chunked? + H.SendResponse("206", "Partial content", myConn); + byteSeek(byteStart); }else{ - if (myMeta.vod){H.SetHeader("Content-Length", byteEnd - byteStart + 1);} + if (M.getVod()){H.SetHeader("Content-Length", byteEnd - byteStart + 1);} /// \todo Switch to chunked? H.SendResponse("200", "OK", myConn); - // HTTP_S.StartResponse(HTTP_R, conn); } parseData = true; wantRequest = false; @@ -475,8 +491,8 @@ namespace Mist{ // Already calculated return; } - DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; - double duration = Trk.lastms - Trk.firstms; + size_t idx = getMainSelectedTrack(); + double duration = M.getLastms(idx) - M.getFirstms(idx); // Calculate the segment size // Segment contains SeekHead, Info, Tracks, Cues (in that order) // Howeveer, SeekHead is dependent on Info/Tracks sizes, so we calculate those first. @@ -484,8 +500,8 @@ namespace Mist{ infoSize = EBML::sizeElemInfo("MistServer " PACKAGE_VERSION, duration); // Calculating Tracks size tracksSize = 0; - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - tracksSize += sizeElemTrackEntry(myMeta.tracks[*it]); + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + tracksSize += sizeElemTrackEntry(it->first); } tracksSize += EBML::sizeElemHead(EBML::EID_TRACKS, tracksSize); // Calculating SeekHead size @@ -504,16 +520,17 @@ namespace Mist{ // Which, in turn, is dependent on the Cluster offsets. // We make this a bit easier by pre-calculating the sizes of all clusters first uint64_t fragNo = 0; - for (std::deque::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){ - uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime(); - uint64_t clusterEnd = clusterStart + it->getDuration(); + DTSC::Fragments fragments(M.fragments(idx)); + for (size_t i = fragments.getFirstValid(); i < fragments.getEndValid(); i++){ + uint64_t clusterStart = M.getTimeForFragmentIndex(idx, i); + uint64_t clusterEnd = clusterStart + fragments.getDuration(i); // The first fragment always starts at time 0, even if the main track does not. if (!fragNo){clusterStart = 0;} uint64_t clusterTmpEnd = clusterEnd; do{ clusterTmpEnd = clusterEnd; // The last fragment always ends at the end, even if the main track does not. - if (fragNo == Trk.fragments.size() - 1){clusterTmpEnd = clusterStart + 30000;} + if (fragNo == fragments.getEndValid() - 1){clusterTmpEnd = clusterStart + 30000;} // Limit clusters to 30 seconds. if (clusterTmpEnd - clusterStart > 30000){clusterTmpEnd = clusterStart + 30000;} uint64_t cSize = clusterSize(clusterStart, clusterTmpEnd); @@ -534,7 +551,7 @@ namespace Mist{ EBML::sizeElemHead(EBML::EID_CUES, cuesSize); uint32_t cuesInside = 0; for (std::map::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ - cuesInside += EBML::sizeElemCuePoint(it->first, Trk.trackID, segmentSize, 0); + cuesInside += EBML::sizeElemCuePoint(it->first, idx + 1, segmentSize, 0); segmentSize += it->second; } cuesSize = cuesInside; diff --git a/src/output/output_ebml.h b/src/output/output_ebml.h index 3eec0087..4250e85a 100644 --- a/src/output/output_ebml.h +++ b/src/output/output_ebml.h @@ -8,8 +8,8 @@ namespace Mist{ static void init(Util::Config *cfg); void onHTTP(); void sendNext(); - virtual void sendHeader(); - uint32_t clusterSize(uint64_t start, uint64_t end); + void sendHeader(); + size_t clusterSize(uint64_t start, uint64_t end); protected: virtual bool inlineRestartCapable() const{return true;} @@ -17,21 +17,21 @@ namespace Mist{ private: bool isRecording(); std::string doctype; - void sendElemTrackEntry(const DTSC::Track &Trk); - uint32_t sizeElemTrackEntry(const DTSC::Track &Trk); - std::string trackCodecID(const DTSC::Track &Trk); + void sendElemTrackEntry(size_t idx); + size_t sizeElemTrackEntry(size_t idx); + std::string trackCodecID(size_t idx); uint64_t currentClusterTime; uint64_t newClusterTime; // VoD-only void calcVodSizes(); - uint64_t segmentSize; // size of complete segment contents (excl. header) - uint32_t tracksSize; // size of Tracks (incl. header) - uint32_t infoSize; // size of Info (incl. header) - uint32_t cuesSize; // size of Cues (excl. header) - uint32_t seekheadSize; // size of SeekHead (incl. header) - uint32_t seekSize; // size of contents of SeekHead (excl. header) - std::map clusterSizes; // sizes of Clusters by start time (incl. header) - void byteSeek(uint64_t startPos); + size_t segmentSize; // size of complete segment contents (excl. header) + size_t tracksSize; // size of Tracks (incl. header) + size_t infoSize; // size of Info (incl. header) + size_t cuesSize; // size of Cues (excl. header) + size_t seekheadSize; // size of SeekHead (incl. header) + size_t seekSize; // size of contents of SeekHead (excl. header) + std::map clusterSizes; // sizes of Clusters (incl. header) + void byteSeek(size_t startPos); }; }// namespace Mist diff --git a/src/output/output_progressive_flv.cpp b/src/output/output_flv.cpp similarity index 62% rename from src/output/output_progressive_flv.cpp rename to src/output/output_flv.cpp index af07eb1b..dc1af181 100644 --- a/src/output/output_progressive_flv.cpp +++ b/src/output/output_flv.cpp @@ -1,9 +1,10 @@ -#include "output_progressive_flv.h" +#include "output_flv.h" +#include namespace Mist{ - OutProgressiveFLV::OutProgressiveFLV(Socket::Connection &conn) : HTTPOutput(conn){} + OutFLV::OutFLV(Socket::Connection &conn) : HTTPOutput(conn){} - void OutProgressiveFLV::init(Util::Config *cfg){ + void OutFLV::init(Util::Config *cfg){ HTTPOutput::init(cfg); capa["name"] = "FLV"; capa["friendly"] = "Flash progressive over HTTP (FLV)"; @@ -45,24 +46,25 @@ namespace Mist{ cfg->addOption("keyframeonly", opt); } - bool OutProgressiveFLV::isRecording(){return config->getString("target").size();} + bool OutFLV::isRecording(){return config->getString("target").size();} - void OutProgressiveFLV::sendNext(){ - // If there are now more selectable tracks, select the new track and do a seek to the current timestamp - if (myMeta.live && selectedTracks.size() < 2){ - static unsigned long long lastMeta = 0; + void OutFLV::sendNext(){ + // If there are now more selectable tracks, select the new track and do a seek to the current + // timestamp + if (M.getLive() && userSelect.size() < 2){ + static uint64_t lastMeta = 0; if (Util::epoch() > lastMeta + 5){ lastMeta = Util::epoch(); - updateMeta(); - if (myMeta.tracks.size() > 1){ + std::set validTracks = getSupportedTracks(); + if (validTracks.size() > 1){ if (selectDefaultTracks()){ INFO_MSG("Track selection changed - resending headers and continuing"); - for (std::set::iterator it = selectedTracks.begin(); - it != selectedTracks.end(); it++){ - if (myMeta.tracks[*it].type == "video" && tag.DTSCVideoInit(myMeta.tracks[*it])){ + for (std::map::iterator it = userSelect.begin(); + it != userSelect.end(); it++){ + if (M.getType(it->first) == "video" && tag.DTSCVideoInit(meta, it->first)){ myConn.SendNow(tag.data, tag.len); } - if (myMeta.tracks[*it].type == "audio" && tag.DTSCAudioInit(myMeta.tracks[*it])){ + if (M.getType(it->first) == "audio" && tag.DTSCAudioInit(meta, it->first)){ myConn.SendNow(tag.data, tag.len); } } @@ -71,10 +73,8 @@ namespace Mist{ } } } - - DTSC::Track &trk = myMeta.tracks[thisPacket.getTrackId()]; - tag.DTSCLoader(thisPacket, trk); - if (trk.codec == "PCM" && trk.size == 16){ + tag.DTSCLoader(thisPacket, M, thisIdx); + if (M.getCodec(thisIdx) == "PCM" && M.getSize(thisIdx) == 16){ char *ptr = tag.getData(); uint32_t ptrSize = tag.getDataLen(); for (uint32_t i = 0; i < ptrSize; i += 2){ @@ -87,7 +87,7 @@ namespace Mist{ if (config->getBool("keyframeonly")){config->is_active = false;} } - void OutProgressiveFLV::sendHeader(){ + void OutFLV::sendHeader(){ if (!isRecording()){ H.Clean(); H.SetHeader("Content-Type", "video/x-flv"); @@ -96,38 +96,43 @@ namespace Mist{ H.SendResponse("200", "OK", myConn); } if (config->getBool("keyframeonly")){ - selectedTracks.clear(); - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (it->second.type == "video"){ - selectedTracks.insert(it->first); + userSelect.clear(); + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (M.getType(*it) == "video"){ + userSelect[*it].reload(streamName, *it); break; } } } myConn.SendNow(FLV::Header, 13); - tag.DTSCMetaInit(myMeta, selectedTracks); + std::set selectedTracks; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + selectedTracks.insert(it->first); + } + tag.DTSCMetaInit(M, selectedTracks); myConn.SendNow(tag.data, tag.len); - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - if (myMeta.tracks[*it].type == "video" && tag.DTSCVideoInit(myMeta.tracks[*it])){ + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + if (M.getType(*it) == "video" && tag.DTSCVideoInit(meta, *it)){ myConn.SendNow(tag.data, tag.len); } - if (myMeta.tracks[*it].type == "audio" && tag.DTSCAudioInit(myMeta.tracks[*it])){ + if (M.getType(*it) == "audio" && tag.DTSCAudioInit(meta, *it)){ myConn.SendNow(tag.data, tag.len); } } if (config->getBool("keyframeonly")){ - unsigned int tid = *selectedTracks.begin(); - int keyNum = myMeta.tracks[tid].keys.rbegin()->getNumber(); - int keyTime = myMeta.tracks[tid].getKey(keyNum).getTime(); - INFO_MSG("Seeking for time %d on track %d key %d", keyTime, tid, keyNum); + size_t tid = userSelect.begin()->first; + DTSC::Keys keys(M.keys(tid)); + uint32_t endKey = keys.getEndValid(); + uint64_t keyTime = keys.getTime(endKey - 1); + INFO_MSG("Seeking for time %" PRIu64 " on track %zu key %" PRIu32, keyTime, tid, endKey - 1); seek(keyTime); } sentHeader = true; } - void OutProgressiveFLV::onHTTP(){ + void OutFLV::onHTTP(){ std::string method = H.method; H.Clean(); diff --git a/src/output/output_progressive_flv.h b/src/output/output_flv.h similarity index 71% rename from src/output/output_progressive_flv.h rename to src/output/output_flv.h index 58961e96..e03bd3cf 100644 --- a/src/output/output_progressive_flv.h +++ b/src/output/output_flv.h @@ -1,9 +1,9 @@ #include "output_http.h" namespace Mist{ - class OutProgressiveFLV : public HTTPOutput{ + class OutFLV : public HTTPOutput{ public: - OutProgressiveFLV(Socket::Connection &conn); + OutFLV(Socket::Connection &conn); static void init(Util::Config *cfg); void onHTTP(); void sendNext(); @@ -17,4 +17,4 @@ namespace Mist{ }; }// namespace Mist -typedef Mist::OutProgressiveFLV mistOut; +typedef Mist::OutFLV mistOut; diff --git a/src/output/output_h264.cpp b/src/output/output_h264.cpp index e7d92c0d..4fbf9064 100644 --- a/src/output/output_h264.cpp +++ b/src/output/output_h264.cpp @@ -64,9 +64,9 @@ namespace Mist{ void OutH264::sendHeader(){ MP4::AVCC avccbox; - unsigned int mainTrack = getMainSelectedTrack(); - if (mainTrack && myMeta.tracks.count(mainTrack)){ - avccbox.setPayload(myMeta.tracks[mainTrack].init); + size_t mainTrack = getMainSelectedTrack(); + if (mainTrack != INVALID_TRACK_ID){ + avccbox.setPayload(M.getInit(mainTrack)); myConn.SendNow(avccbox.asAnnexB()); } sentHeader = true; diff --git a/src/output/output_hds.cpp b/src/output/output_hds.cpp index 3a9139da..bafcd60e 100644 --- a/src/output/output_hds.cpp +++ b/src/output/output_hds.cpp @@ -7,23 +7,22 @@ namespace Mist{ void OutHDS::getTracks(){ - /// \todo Why do we have only one audio track option? videoTracks.clear(); - audioTrack = 0; + audioTrack = INVALID_TRACK_ID; JSON::Value &vidCapa = capa["codecs"][0u][0u]; JSON::Value &audCapa = capa["codecs"][0u][1u]; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); ++it){ jsonForEach(vidCapa, itb){ - if (it->second.codec == (*itb).asStringRef()){ - videoTracks.insert(it->first); + if (M.getCodec(*it) == itb->asStringRef()){ + videoTracks.insert(*it); break; } } - if (!audioTrack){ + if (audioTrack == INVALID_TRACK_ID){ jsonForEach(audCapa, itb){ - if (it->second.codec == (*itb).asStringRef()){ - audioTrack = it->first; + if (M.getCodec(*it) == itb->asStringRef()){ + audioTrack = *it; break; } } @@ -34,18 +33,19 @@ namespace Mist{ ///\brief Builds a bootstrap for use in HTTP Dynamic streaming. ///\param tid The track this bootstrap is generated for. ///\return The generated bootstrap. - std::string OutHDS::dynamicBootstrap(int tid){ - updateMeta(); + std::string OutHDS::dynamicBootstrap(size_t idx){ + DTSC::Fragments fragments(M.fragments(idx)); + DTSC::Keys keys(M.keys(idx)); std::string empty; MP4::ASRT asrt; asrt.setUpdate(false); asrt.setVersion(1); // asrt.setQualityEntry(empty, 0); - if (myMeta.live){ + if (M.getLive()){ asrt.setSegmentRun(1, 4294967295ul, 0); }else{ - asrt.setSegmentRun(1, myMeta.tracks[tid].fragments.size(), 0); + asrt.setSegmentRun(1, fragments.getValidCount(), 0); } MP4::AFRT afrt; @@ -54,26 +54,21 @@ namespace Mist{ afrt.setTimeScale(1000); // afrt.setQualityEntry(empty, 0); MP4::afrt_runtable afrtrun; - int i = 0; - int j = 0; - if (myMeta.tracks[tid].fragments.size()){ - std::deque::iterator fragIt = myMeta.tracks[tid].fragments.begin(); - unsigned int firstTime = myMeta.tracks[tid].getKey(fragIt->getNumber()).getTime(); - while (fragIt != myMeta.tracks[tid].fragments.end()){ - if (myMeta.vod || fragIt->getDuration() > 0){ - afrtrun.firstFragment = myMeta.tracks[tid].missedFrags + j + 1; - afrtrun.firstTimestamp = myMeta.tracks[tid].getKey(fragIt->getNumber()).getTime() - firstTime; - if (fragIt->getDuration() > 0){ - afrtrun.duration = fragIt->getDuration(); - }else{ - afrtrun.duration = myMeta.tracks[tid].lastms - afrtrun.firstTimestamp; - } - afrt.setFragmentRun(afrtrun, i); - ++i; + size_t i = 0; + size_t j = 0; + uint64_t firstTime = keys.getTime(fragments.getFirstKey(fragments.getFirstValid())); + for (size_t fragIdx = fragments.getFirstValid() + 1; fragIdx < fragments.getEndValid(); ++fragIdx){ + if (M.getVod() || fragments.getDuration(fragIdx) > 0){ + afrtrun.firstFragment = M.getMissedFragments(idx) + j + 1; + afrtrun.firstTimestamp = keys.getTime(fragments.getFirstKey(fragIdx)) - firstTime; + if (fragments.getDuration(fragIdx) > 0){ + afrtrun.duration = fragments.getDuration(fragIdx); + }else{ + afrtrun.duration = M.getLastms(idx) - afrtrun.firstTimestamp; } - ++j; - ++fragIt; + afrt.setFragmentRun(afrtrun, i++); } + ++j; } MP4::ABST abst; @@ -82,15 +77,15 @@ namespace Mist{ abst.setProfile(0); abst.setUpdate(false); abst.setTimeScale(1000); - abst.setLive(myMeta.live); - abst.setCurrentMediaTime(myMeta.tracks[tid].lastms); + abst.setLive(M.getLive()); + abst.setCurrentMediaTime(M.getLastms(idx)); abst.setSmpteTimeCodeOffset(0); abst.setMovieIdentifier(streamName); abst.setSegmentRunTable(asrt, 0); abst.setFragmentRunTable(afrt, 0); - DEBUG_MSG(DLVL_VERYHIGH, "Sending bootstrap: %s", abst.toPrettyString(0).c_str()); - return std::string((char *)abst.asBox(), (int)abst.boxedSize()); + VERYHIGH_MSG("Sending bootstrap: %s", abst.toPrettyString(0).c_str()); + return std::string(abst.asBox(), abst.boxedSize()); } ///\brief Builds an index file for HTTP Dynamic streaming. @@ -103,53 +98,53 @@ namespace Mist{ Result << " " << streamName << "" << std::endl; Result << " video/mp4" << std::endl; Result << " streaming" << std::endl; - if (myMeta.vod){ - Result << " " << myMeta.tracks[*videoTracks.begin()].lastms / 1000 + if (M.getVod()){ + Result << " " << M.getLastms(videoTracks.size() ? *videoTracks.begin() : audioTrack) / 1000 << ".000" << std::endl; Result << " recorded" << std::endl; }else{ Result << " 0.00" << std::endl; Result << " live" << std::endl; } - for (std::set::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){ + for (std::set::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){ Result << " " "" << std::endl; Result << " " << std::endl; + << M.getHeight(*it) << "\">" << std::endl; Result << " AgAKb25NZXRhRGF0YQMAAAk=" << std::endl; Result << " " << std::endl; } Result << "" << std::endl; - DEBUG_MSG(DLVL_HIGH, "Sending manifest: %s", Result.str().c_str()); + HIGH_MSG("Sending manifest: %s", Result.str().c_str()); return Result.str(); }// BuildManifest OutHDS::OutHDS(Socket::Connection &conn) : HTTPOutput(conn){ uaDelay = 0; realTime = 0; - audioTrack = 0; + audioTrack = INVALID_TRACK_ID; playUntil = 0; } @@ -186,15 +181,14 @@ namespace Mist{ void OutHDS::sendNext(){ if (thisPacket.getTime() >= playUntil){ - VERYHIGH_MSG("Done sending fragment (%llu >= %llu)", thisPacket.getTime(), playUntil); + VERYHIGH_MSG("Done sending fragment (%" PRIu64 " >= %" PRIu64 ")", thisPacket.getTime(), playUntil); stop(); wantRequest = true; H.Chunkify("", 0, myConn); return; } - DTSC::Track &trk = myMeta.tracks[thisPacket.getTrackId()]; - tag.DTSCLoader(thisPacket, trk); - if (trk.codec == "PCM" && trk.size == 16){ + tag.DTSCLoader(thisPacket, M, thisIdx); + if (M.getCodec(thisIdx) == "PCM" && M.getSize(thisIdx) == 16){ char *ptr = tag.getData(); uint32_t ptrSize = tag.getDataLen(); for (uint32_t i = 0; i < ptrSize; i += 2){ @@ -230,47 +224,45 @@ namespace Mist{ if (H.url.find("f4m") == std::string::npos){ initialize(); std::string tmp_qual = H.url.substr(H.url.find("/", 10) + 1); - unsigned int tid; - unsigned int fragNum; - tid = atoi(tmp_qual.substr(0, tmp_qual.find("Seg") - 1).c_str()); + size_t idx = atoi(tmp_qual.substr(0, tmp_qual.find("Seg") - 1).c_str()); + if (idx == INVALID_TRACK_ID){FAIL_MSG("Requested fragment for invalid track id");} int temp; temp = H.url.find("Seg") + 3; temp = H.url.find("Frag") + 4; - fragNum = atoi(H.url.substr(temp).c_str()) - 1; - DEBUG_MSG(DLVL_MEDIUM, "Video track %d, fragment %d", tid, fragNum); - if (!audioTrack){getTracks();} - unsigned int mstime = 0; - unsigned int mslen = 0; - if (fragNum < (unsigned int)myMeta.tracks[tid].missedFrags){ + size_t fragIdx = atoi(H.url.substr(temp).c_str()) - 1; + MEDIUM_MSG("Video track %zu, fragment %zu", idx, fragIdx); + if (audioTrack == INVALID_TRACK_ID){getTracks();} + uint64_t mstime = 0; + uint64_t mslen = 0; + if (fragIdx < M.getMissedFragments(idx)){ H.Clean(); H.setCORSHeaders(); H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be " "served.\n"); H.SendResponse("412", "Fragment out of range", myConn); H.Clean(); // clean for any possible next requests - std::cout << "Fragment " << fragNum << " too old" << std::endl; + FAIL_MSG("Fragment %zu too old", fragIdx); return; } // delay if we don't have the next fragment available yet unsigned int timeout = 0; - while (myConn && fragNum >= myMeta.tracks[tid].missedFrags + myMeta.tracks[tid].fragments.size() - 1){ + DTSC::Fragments fragments(M.fragments(idx)); + DTSC::Keys keys(M.keys(idx)); + while (myConn && fragIdx >= fragments.getEndValid() - 1){ // time out after 21 seconds if (++timeout > 42){ - myConn.close(); + onFail("Timeout triggered", true); break; } Util::wait(500); - updateMeta(); } - mstime = myMeta.tracks[tid] - .getKey(myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getNumber()) - .getTime(); - mslen = myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getDuration(); - VERYHIGH_MSG("Playing from %llu for %llu ms", mstime, mslen); + mstime = keys.getTime(fragments.getFirstKey(fragIdx)); + mslen = fragments.getDuration(fragIdx); + VERYHIGH_MSG("Playing from %" PRIu64 " for %" PRIu64 " ms", mstime, mslen); - selectedTracks.clear(); - selectedTracks.insert(tid); - if (audioTrack){selectedTracks.insert(audioTrack);} + userSelect.clear(); + userSelect[idx].reload(streamName, idx); + if (audioTrack != INVALID_TRACK_ID){userSelect[audioTrack].reload(streamName, audioTrack);} seek(mstime); playUntil = mstime + mslen; @@ -284,19 +276,18 @@ namespace Mist{ } H.StartResponse(H, myConn); // send the bootstrap - std::string bootstrap = dynamicBootstrap(tid); - H.Chunkify(bootstrap, myConn); + H.Chunkify(dynamicBootstrap(idx), myConn); // send a zero-size mdat, meaning it stretches until end of file. H.Chunkify("\000\000\000\000mdat", 8, myConn); // send init data, if needed. - if (audioTrack > 0 && myMeta.tracks[audioTrack].init != ""){ - if (tag.DTSCAudioInit(myMeta.tracks[audioTrack])){ + if (audioTrack != INVALID_TRACK_ID && M.getInit(audioTrack) != ""){ + if (tag.DTSCAudioInit(meta, audioTrack)){ tag.tagTime(mstime); H.Chunkify(tag.data, tag.len, myConn); } } - if (tid > 0){ - if (tag.DTSCVideoInit(myMeta.tracks[tid])){ + if (idx != INVALID_TRACK_ID){ + if (tag.DTSCVideoInit(meta, idx)){ tag.tagTime(mstime); H.Chunkify(tag.data, tag.len, myConn); } @@ -305,8 +296,6 @@ namespace Mist{ wantRequest = false; }else{ initialize(); - std::stringstream tmpstr; - myMeta.toPrettyString(tmpstr); H.Clean(); H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Cache-Control", "no-cache"); diff --git a/src/output/output_hds.h b/src/output/output_hds.h index a18187f3..e6e7e127 100644 --- a/src/output/output_hds.h +++ b/src/output/output_hds.h @@ -14,11 +14,11 @@ namespace Mist{ protected: void getTracks(); - std::string dynamicBootstrap(int tid); + std::string dynamicBootstrap(size_t idx); std::string dynamicIndex(); - std::set videoTracks; ///<< Holds valid video tracks for playback - long long int audioTrack; ///<< Holds audio track ID for playback - long long unsigned int playUntil; + std::set videoTracks; ///<< Holds valid video tracks for playback + size_t audioTrack; ///<< Holds audio track ID for playback + uint64_t playUntil; FLV::Tag tag; }; }// namespace Mist diff --git a/src/output/output_hls.cpp b/src/output/output_hls.cpp index af4e4e1e..d7cfd856 100644 --- a/src/output/output_hls.cpp +++ b/src/output/output_hls.cpp @@ -6,10 +6,11 @@ namespace Mist{ bool OutHLS::isReadyForPlay(){ - if (myMeta.tracks.size()){ - if (myMeta.mainTrack().fragments.size() > 4){return true;} - } - return false; + if (!M.getValidTracks().size()){return false;} + uint32_t mainTrack = M.mainTrack(); + if (mainTrack == INVALID_TRACK_ID){return false;} + DTSC::Fragments fragments(M.fragments(mainTrack)); + return fragments.getValidCount() > 4; } ///\brief Builds an index file for HTTP Live streaming. @@ -18,256 +19,139 @@ namespace Mist{ std::stringstream result; selectDefaultTracks(); result << "#EXTM3U\r\n"; - int audioId = -1; - unsigned int vidTracks = 0; + size_t audioId = INVALID_TRACK_ID; + size_t vidTracks = 0; bool hasSubs = false; - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ - if (audioId == -1 && myMeta.tracks[*it].type == "audio"){audioId = *it;} - if (!hasSubs && myMeta.tracks[*it].codec == "subtitle"){hasSubs = true;} + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); ++it){ + if (audioId == INVALID_TRACK_ID && M.getType(it->first) == "audio"){audioId = it->first;} + if (!hasSubs && M.getCodec(it->first) == "subtitle"){hasSubs = true;} } - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ - if (myMeta.tracks[*it].type == "video"){ - vidTracks++; - int bWidth = myMeta.tracks[*it].bps; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); ++it){ + if (M.getType(it->first) == "video"){ + ++vidTracks; + int bWidth = M.getBps(it->first); if (bWidth < 5){bWidth = 5;} - if (audioId != -1){bWidth += myMeta.tracks[audioId].bps;} + if (audioId != INVALID_TRACK_ID){bWidth += M.getBps(audioId);} result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8); - result << ",RESOLUTION=" << myMeta.tracks[*it].width << "x" << myMeta.tracks[*it].height; - if (myMeta.tracks[*it].fpks){ - result << ",FRAME-RATE=" << (float)myMeta.tracks[*it].fpks / 1000; + result << ",RESOLUTION=" << M.getWidth(it->first) << "x" << M.getHeight(it->first); + if (M.getFpks(it->first)){ + result << ",FRAME-RATE=" << (float)M.getFpks(it->first) / 1000; } if (hasSubs){result << ",SUBTITLES=\"sub1\"";} result << ",CODECS=\""; - result << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init); - if (audioId != -1){ - result << "," << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init); + result << Util::codecString(M.getCodec(it->first), M.getInit(it->first)); + if (audioId != INVALID_TRACK_ID){ + result << "," << Util::codecString(M.getCodec(audioId), M.getInit(audioId)); } - result << "\""; - result << "\r\n"; - result << *it; - if (audioId != -1){result << "_" << audioId;} + result << "\"\r\n" << it->first; + if (audioId != INVALID_TRACK_ID){result << "_" << audioId;} if (hasSessionIDs()){ result << "/index.m3u8?sessId=" << getpid() << "\r\n"; }else{ result << "/index.m3u8\r\n"; } - }else if (myMeta.tracks[*it].codec == "subtitle"){ + }else if (M.getCodec(it->first) == "subtitle"){ - if (myMeta.tracks[*it].lang.empty()){myMeta.tracks[*it].lang = "und";} + if (M.getLang(it->first).empty()){meta.setLang(it->first, "und");} - result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << myMeta.tracks[*it].lang - << "\",NAME=\"" << Encodings::ISO639::decode(myMeta.tracks[*it].lang) - << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << *it << "/index.m3u8\"" + result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << M.getLang(it->first) + << "\",NAME=\"" << Encodings::ISO639::decode(M.getLang(it->first)) + << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << it->first << "/index.m3u8\"" << "\r\n"; } } - if (!vidTracks && audioId){ - result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[audioId].bps * 8); - result << ",CODECS=\"" - << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init) << "\""; + if (!vidTracks && audioId != INVALID_TRACK_ID){ + result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (M.getBps(audioId) * 8); + result << ",CODECS=\"" << Util::codecString(M.getCodec(audioId), M.getInit(audioId)) << "\""; result << "\r\n"; result << audioId << "/index.m3u8\r\n"; } - DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str()); return result.str(); } - std::string OutHLS::pushLiveIndex(){ - std::stringstream result; - result << "#EXTM3U\r\n"; - std::set audioTracks; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3" || - it->second.codec == "MP2"){ - audioTracks.insert(it->first); - } - } - if (!audioTracks.size()){audioTracks.insert(-1);} - unsigned int vidTracks = 0; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (it->second.codec == "H264" || it->second.codec == "HEVC" || it->second.codec == "MPEG2"){ - for (std::set::iterator audIt = audioTracks.begin(); audIt != audioTracks.end(); audIt++){ - vidTracks++; - int bWidth = it->second.bps; - if (bWidth < 5){bWidth = 5;} - if (*audIt != -1){bWidth += myMeta.tracks[*audIt].bps;} - result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) << "\r\n"; - result << it->first; - if (*audIt != -1){result << "_" << *audIt;} - result << "/index.m3u8\r\n"; - } - } - } - if (!vidTracks && audioTracks.size()){ - result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[*audioTracks.begin()].bps * 8) - << "\r\n"; - result << *audioTracks.begin() << "/index.m3u8\r\n"; - } - return result.str(); - } - - std::string OutHLS::pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime){ - updateMeta(); + std::string OutHLS::liveIndex(size_t tid, const std::string &sessId){ std::stringstream result; // parse single track - result << "#EXTM3U\r\n#EXT-X-TARGETDURATION:" << (myMeta.tracks[tid].biggestFragment() / 1000) + 1 << "\r\n"; + uint32_t targetDuration = (M.biggestFragment(tid) / 1000) + 1; + result << "#EXTM3U\r\n#EXT-X-VERSION:"; - std::deque lines; - unsigned int skippedLines = 0; - for (std::deque::iterator it = myMeta.tracks[tid].fragments.begin(); - it != myMeta.tracks[tid].fragments.end(); it++){ - long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime(); - long long duration = it->getDuration(); - if (duration <= 0){duration = myMeta.tracks[tid].lastms - starttime;} - if (starttime < bTime){skippedLines++;} - if (starttime >= bTime && (starttime + duration) <= eTime){ - char lineBuf[400]; - snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts\r\n", - ((duration + 500) / 1000), starttime, starttime + duration); - lines.push_back(lineBuf); - } + result << (M.getEncryption(tid) == "" ? "3" : "5"); + + result << "\r\n#EXT-X-TARGETDURATION:" << targetDuration << "\r\n"; + + if (M.getEncryption(tid) != ""){ + result << "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=\""; + result << "urlHere"; + result << "\",KEYFORMAT=\"com.apple.streamingkeydelivery" << std::endl; } - result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n"; - - while (lines.size()){ - result << lines.front(); - lines.pop_front(); - } - if (!myMeta.live && eTime >= myMeta.tracks[tid].lastms){result << "#EXT-X-ENDLIST\r\n";} - return result.str(); - } - - std::string OutHLS::liveIndex(int tid, std::string &sessId){ - updateMeta(); - std::stringstream result; - // parse single track - uint32_t target_dur = (myMeta.tracks[tid].biggestFragment() / 1000) + 1; - result << "#EXTM3U\r\n#EXT-X-VERSION:3\r\n#EXT-X-TARGETDURATION:" << target_dur << "\r\n"; - std::deque lines; - std::deque durs; - uint32_t total_dur = 0; - for (std::deque::iterator it = myMeta.tracks[tid].fragments.begin(); - it != myMeta.tracks[tid].fragments.end(); it++){ - long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime(); - long long duration = it->getDuration(); - if (duration <= 0){duration = myMeta.tracks[tid].lastms - starttime;} + std::deque durations; + uint32_t totalDuration = 0; + DTSC::Keys keys(M.keys(tid)); + DTSC::Fragments fragments(M.fragments(tid)); + uint32_t firstFragment = fragments.getFirstValid(); + uint32_t endFragment = fragments.getEndValid(); + for (int i = firstFragment; i < endFragment; i++){ + uint64_t duration = fragments.getDuration(i); + size_t keyNumber = fragments.getFirstKey(i); + uint64_t startTime = keys.getTime(keyNumber); + if (!duration){duration = M.getLastms(tid) - startTime;} + double floatDur = (double)duration / 1000; char lineBuf[400]; - if (myMeta.tracks[tid].codec == "subtitle"){ - snprintf(lineBuf, 400, "#EXTINF:%f,\r\n../../../%s.vtt?track=%d&from=%lld&to=%lld\r\n", - (double)duration / 1000, streamName.c_str(), tid, starttime, starttime + duration); + if (M.getCodec(tid) == "subtitle"){ + snprintf(lineBuf, 400, "#EXTINF:%f,\r\n../../../%s.vtt?track=%zu&from=%" PRIu64 "&to=%" PRIu64 "\r\n", + (double)duration / 1000, streamName.c_str(), tid, startTime, startTime + duration); }else{ if (sessId.size()){ - snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts?sessId=%s\r\n", - (double)duration / 1000, starttime, starttime + duration, sessId.c_str()); + snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%" PRIu64 "_%" PRIu64 ".ts?sessId=%s\r\n", + floatDur, startTime, startTime + duration, sessId.c_str()); }else{ - snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts\r\n", (double)duration / 1000, - starttime, starttime + duration); + snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%" PRIu64 "_%" PRIu64 ".ts\r\n", floatDur, + startTime, startTime + duration); } } - durs.push_back(duration); - total_dur += duration; + totalDuration += duration; + durations.push_back(duration); lines.push_back(lineBuf); } - unsigned int skippedLines = 0; - if (myMeta.live && lines.size()){ + size_t skippedLines = 0; + if (M.getLive() && lines.size()){ // only print the last segment when VoD lines.pop_back(); - total_dur -= durs.back(); - durs.pop_back(); + totalDuration -= durations.back(); + durations.pop_back(); // skip the first two segments when live, unless that brings us under 4 target durations - while ((total_dur - durs.front()) > (target_dur * 4000) && skippedLines < 2){ + while ((totalDuration - durations.front()) > (targetDuration * 4000) && skippedLines < 2){ lines.pop_front(); - total_dur -= durs.front(); - durs.pop_front(); + totalDuration -= durations.front(); + durations.pop_front(); ++skippedLines; } /*LTS-START*/ - // remove lines to reduce size towards listlimit setting - but keep at least 4X target duration available - if (config->getInteger("listlimit")){ - unsigned long listlimit = config->getInteger("listlimit"); - while (lines.size() > listlimit && (total_dur - durs.front()) > (target_dur * 4000)){ + // remove lines to reduce size towards listlimit setting - but keep at least 4X target + // duration available + uint64_t listlimit = config->getInteger("listlimit"); + if (listlimit){ + while (lines.size() > listlimit && (totalDuration - durations.front()) > (targetDuration * 4000)){ lines.pop_front(); - total_dur -= durs.front(); - durs.pop_front(); + totalDuration -= durations.front(); + durations.pop_front(); ++skippedLines; } } /*LTS-END*/ } - result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n"; + result << "#EXT-X-MEDIA-SEQUENCE:" << M.getMissedFragments(tid) + skippedLines << "\r\n"; - while (lines.size()){ - result << lines.front(); - lines.pop_front(); + for (std::deque::iterator it = lines.begin(); it != lines.end(); it++){ + result << *it; } - if (!myMeta.live || total_dur == 0){result << "#EXT-X-ENDLIST\r\n";} - DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str()); + if (!M.getLive() || !totalDuration){result << "#EXT-X-ENDLIST\r\n";} + HIGH_MSG("Sending this index: %s", result.str().c_str()); return result.str(); - }// liveIndex - - std::string OutHLS::generatePushList(){ - updateMeta(); - std::set videoTracks; - std::set audioTracks; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3"){ - audioTracks.insert(it->first); - } - if (it->second.codec == "H264" || it->second.codec == "HEVC"){ - videoTracks.insert(it->first); - } - } - JSON::Value result; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - std::stringstream tid; - tid << it->second.trackID; - result["tracks"][tid.str()] = it->second.toJSON(true); - } - for (std::set::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){ - for (std::set::iterator it2 = audioTracks.begin(); it2 != audioTracks.end(); it2++){ - JSON::Value quality; - std::stringstream identifier; - identifier << "/" << *it << "_" << *it2; - quality["index"] = "/push" + identifier.str() + "/index_\%llu_\%llu.m3u8"; - quality["segment"] = identifier.str() + "/\%llu_\%llu.ts"; - quality["video"] = *it; - quality["audio"] = *it2; - quality["id"] = identifier.str(); - std::deque::iterator it3 = myMeta.tracks[*it].fragments.begin(); - for (int i = 0; i < 2; i++){ - if (it3 != myMeta.tracks[*it].fragments.end()){++it3;} - } - for (; it3 != myMeta.tracks[*it].fragments.end(); it3++){ - if (myMeta.live && it3 == (myMeta.tracks[*it].fragments.end() - 1)){ - // Skip the current last fragment if we are live - continue; - } - uint64_t starttime = myMeta.tracks[*it].getKey(it3->getNumber()).getTime(); - std::stringstream line; - uint64_t duration = it3->getDuration(); - if (duration <= 0){duration = myMeta.tracks[*it].lastms - starttime;} - std::stringstream segmenturl; - segmenturl << identifier.str() << "/" << starttime << "_" << duration + starttime << ".ts"; - JSON::Value segment; - // segment["url"] = segmenturl.str(); - segment["time"] = starttime; - segment["duration"] = duration; - segment["number"] = (uint64_t)it3->getNumber(); - quality["segments"].append(segment); - } - result["qualities"].append(quality); - } - } - return result.toString(); - ; } OutHLS::OutHLS(Socket::Connection &conn) : TSOutput(conn){ @@ -286,7 +170,6 @@ namespace Mist{ "Segmented streaming in Apple (TS-based) format over HTTP ( = HTTP Live Streaming)"; capa["url_rel"] = "/hls/$/index.m3u8"; capa["url_prefix"] = "/hls/$/"; - capa["url_pushlist"] = "/hls/$/push/list"; capa["codecs"][0u][0u].append("+HEVC"); capa["codecs"][0u][1u].append("+H264"); capa["codecs"][0u][2u].append("+MPEG2"); @@ -380,11 +263,12 @@ namespace Mist{ return; } + std::string userAgent = H.GetHeader("User-Agent"); bool VLCworkaround = false; - if (H.GetHeader("User-Agent").substr(0, 3) == "VLC"){ - std::string vlcver = H.GetHeader("User-Agent").substr(4); + if (userAgent.substr(0, 3) == "VLC"){ + std::string vlcver = userAgent.substr(4); if (vlcver[0] == '0' || vlcver[0] == '1' || (vlcver[0] == '2' && vlcver[2] < '2')){ - DEBUG_MSG(DLVL_INFO, "Enabling VLC version < 2.2.0 bug workaround."); + INFO_MSG("Enabling VLC version < 2.2.0 bug workaround."); VLCworkaround = true; } } @@ -392,82 +276,40 @@ namespace Mist{ initialize(); if (!keepGoing()){return;} - if (H.url.substr(5 + streamName.size(), 5) == "/push"){ - std::string relPushUrl = H.url.substr(10 + streamName.size()); - H.Clean(); - if (relPushUrl == "/list"){ - H.SetBody(generatePushList()); - H.SendResponse("200", "OK", myConn); - H.Clean(); - return; - } - H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); - if (relPushUrl == "/index.m3u8"){ - H.setCORSHeaders(); - H.SetBody(pushLiveIndex()); - H.SendResponse("200", "OK", myConn); - H.Clean(); // clean for any possible next requests - return; - }else{ - unsigned int vTrack; - unsigned int aTrack; - unsigned long long bTime; - unsigned long long eTime; - if (sscanf(relPushUrl.c_str(), "/%u_%u/index_%llu_%llu.m3u", &vTrack, &aTrack, &bTime, &eTime) == 4){ - if (eTime < bTime){eTime = bTime;} - H.setCORSHeaders(); - H.SetBody(pushLiveIndex(vTrack, bTime, eTime)); - H.SendResponse("200", "OK", myConn); - H.Clean(); // clean for any possible next requests - return; - } - } - H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n"); - myConn.SendNow(H.BuildResponse("404", "URL mismatch")); - H.Clean(); // clean for any possible next requests - return; - }else if (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"){ - size_t slashPos = H.getUrl().find('/', 5); - std::string tmpStr = H.getUrl().substr(slashPos); - long long unsigned int from; - if (sscanf(tmpStr.c_str(), "/%u_%u/%llu_%llu.ts", &vidTrack, &audTrack, &from, &until) != 4){ - if (sscanf(tmpStr.c_str(), "/%u/%llu_%llu.ts", &vidTrack, &from, &until) != 3){ - DEBUG_MSG(DLVL_MEDIUM, "Could not parse URL: %s", H.getUrl().c_str()); + if (H.url.find(".m3u") == std::string::npos){ + std::string tmpStr = H.getUrl().substr(5 + streamName.size()); + uint64_t from; + if (sscanf(tmpStr.c_str(), "/%zu_%zu/%" PRIu64 "_%" PRIu64 ".ts", &vidTrack, &audTrack, &from, &until) != 4){ + if (sscanf(tmpStr.c_str(), "/%zu/%" PRIu64 "_%" PRIu64 ".ts", &vidTrack, &from, &until) != 3){ + MEDIUM_MSG("Could not parse URL: %s", H.getUrl().c_str()); H.Clean(); H.setCORSHeaders(); H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n"); myConn.SendNow(H.BuildResponse("404", "URL mismatch")); H.Clean(); // clean for any possible next requests return; - }else{ - selectedTracks.clear(); - selectedTracks.insert(vidTrack); } + userSelect.clear(); + userSelect[vidTrack].reload(streamName, vidTrack); }else{ - selectedTracks.clear(); - selectedTracks.insert(vidTrack); - selectedTracks.insert(audTrack); + userSelect.clear(); + userSelect[vidTrack].reload(streamName, vidTrack); + userSelect[audTrack].reload(streamName, audTrack); } - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (it->second.codec == "ID3"){selectedTracks.insert(it->first);} + std::set validTracks = getSupportedTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); ++it){ + if (M.getCodec(*it) == "ID3"){userSelect[*it].reload(streamName, *it);} } - // Keep a reference to the main track - // This is called vidTrack, even for audio-only streams - DTSC::Track &Trk = myMeta.tracks[vidTrack]; - - if (myMeta.live){ - if (from < Trk.firstms){ - H.Clean(); - H.setCORSHeaders(); - H.SetBody("The requested fragment is no longer kept in memory on the server and cannot " - "be served.\n"); - myConn.SendNow(H.BuildResponse("404", "Fragment out of range")); - H.Clean(); // clean for any possible next requests - WARN_MSG("Fragment @ %llu too old", from); - return; - } + if (M.getLive() && from < M.getFirstms(vidTrack)){ + H.Clean(); + H.setCORSHeaders(); + H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be " + "served.\n"); + myConn.SendNow(H.BuildResponse("404", "Fragment out of range")); + H.Clean(); // clean for any possible next requests + WARN_MSG("Fragment @ %" PRIu64 " too old", from); + return; } H.SetHeader("Content-Type", "video/mp2t"); @@ -487,10 +329,10 @@ namespace Mist{ H.StartResponse(H, myConn, VLCworkaround || config->getBool("nonchunked")); // we assume whole fragments - but timestamps may be altered at will - uint32_t fragIndice = Trk.timeToFragnum(from); - contPAT = Trk.missedFrags + fragIndice; // PAT continuity counter - contPMT = Trk.missedFrags + fragIndice; // PMT continuity counter - contSDT = Trk.missedFrags + fragIndice; // SDT continuity counter + uint32_t fragIndice = M.getFragmentIndexForTime(vidTrack, from); + contPAT = M.getMissedFragments(vidTrack) + fragIndice; // PAT continuity counter + contPMT = M.getMissedFragments(vidTrack) + fragIndice; // PMT continuity counter + contSDT = M.getMissedFragments(vidTrack) + fragIndice; // SDT continuity counter packCounter = 0; parseData = true; wantRequest = false; @@ -501,8 +343,7 @@ namespace Mist{ std::string request = H.url.substr(H.url.find("/", 5) + 1); H.Clean(); H.setCORSHeaders(); - H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); - if (!myMeta.tracks.size()){ + if (!M.getValidTracks().size()){ H.SendResponse("404", "Not online or found", myConn); H.Clean(); return; @@ -516,8 +357,14 @@ namespace Mist{ if (request.find("/") == std::string::npos){ manifest = liveIndex(); }else{ - int selectId = atoi(request.substr(0, request.find("/")).c_str()); - manifest = liveIndex(selectId, sessId); + size_t idx = atoi(request.substr(0, request.find("/")).c_str()); + if (!M.getValidTracks().count(idx)){ + H.SendResponse("404", "No corresponding track found", myConn); + H.Clean(); + return; + } + + manifest = liveIndex(idx, sessId); } H.SetBody(manifest); H.SendResponse("200", "OK", myConn); @@ -532,10 +379,9 @@ namespace Mist{ parseData = false; // Ensure alignment of contCounters for selected tracks, to prevent discontinuities. - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ - DTSC::Track &Trk = myMeta.tracks[*it]; - uint32_t pkgPid = 255 + *it; - int &contPkg = contCounters[pkgPid]; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + uint32_t pkgPid = 255 + it->first; + uint16_t &contPkg = contCounters[pkgPid]; if (contPkg % 16 != 0){ packData.clear(); packData.setPID(pkgPid); @@ -556,7 +402,7 @@ namespace Mist{ TSOutput::sendNext(); } - void OutHLS::sendTS(const char *tsData, unsigned int len){H.Chunkify(tsData, len, myConn);} + void OutHLS::sendTS(const char *tsData, size_t len){H.Chunkify(tsData, len, myConn);} void OutHLS::onFail(const std::string &msg, bool critical){ if (H.url.find(".m3u") == std::string::npos){ diff --git a/src/output/output_hls.h b/src/output/output_hls.h index 6798eb01..40eb2f11 100644 --- a/src/output/output_hls.h +++ b/src/output/output_hls.h @@ -7,7 +7,7 @@ namespace Mist{ OutHLS(Socket::Connection &conn); ~OutHLS(); static void init(Util::Config *cfg); - void sendTS(const char *tsData, unsigned int len = 188); + void sendTS(const char *tsData, size_t len = 188); void sendNext(); void onHTTP(); bool isReadyForPlay(); @@ -19,17 +19,11 @@ namespace Mist{ bool hasSessionIDs(){return !config->getBool("mergesessions");} std::string liveIndex(); - std::string liveIndex(int tid, std::string &sessId); + std::string liveIndex(size_t tid, const std::string &sessId); - std::string pushLiveIndex(); - std::string pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime); - - std::string generatePushList(); - int canSeekms(unsigned int ms); - int keysToSend; - unsigned int vidTrack; - unsigned int audTrack; - long long unsigned int until; + size_t vidTrack; + size_t audTrack; + uint64_t until; }; }// namespace Mist diff --git a/src/output/output_hss.cpp b/src/output/output_hss.cpp deleted file mode 100644 index a148139f..00000000 --- a/src/output/output_hss.cpp +++ /dev/null @@ -1,597 +0,0 @@ -#include "output_hss.h" -#include -#include -#include -#include -#include -#include -#include /*LTS*/ -#include -#include -#include /*LTS*/ -#include -#include - -///\todo Maybe move to util? -long long unsigned int binToInt(std::string &binary){ - long long int result = 0; - for (int i = 0; i < 8; i++){ - result <<= 8; - result += binary[i]; - } - return result; -} - -std::string intToBin(long long unsigned int number){ - std::string result; - result.resize(8); - for (int i = 7; i >= 0; i--){ - result[i] = number & 0xFF; - number >>= 8; - } - return result; -} - -std::string toUTF16(std::string original){ - std::string result; - result += (char)0xFF; - result += (char)0xFE; - for (std::string::iterator it = original.begin(); it != original.end(); it++){ - result += (*it); - result += (char)0x00; - } - return result; -} - -/// Converts bytes per second and track ID into a single bits per second value, where the last two -/// digits are the track ID. Breaks for track IDs > 99. But really, this is MS-SS, so who cares..? -uint64_t bpsAndIdToBitrate(uint32_t bps, uint64_t tid){ - return ((uint64_t)((bps * 8) / 100)) * 100 + tid; -} - -namespace Mist{ - OutHSS::OutHSS(Socket::Connection &conn) : HTTPOutput(conn){ - uaDelay = 0; - realTime = 0; - } - OutHSS::~OutHSS(){} - - void OutHSS::init(Util::Config *cfg){ - HTTPOutput::init(cfg); - capa["name"] = "HSS"; - capa["friendly"] = "Microsoft segmented over HTTP (HSS)"; - capa["desc"] = "Segmented streaming in Microsoft Silverlight (fMP4-based) format over HTTP ( = " - "HTTP Smooth Streaming)"; - capa["url_rel"] = "/smooth/$.ism/Manifest"; - capa["url_prefix"] = "/smooth/$.ism/"; - capa["codecs"][0u][0u].append("H264"); - capa["codecs"][0u][1u].append("AAC"); - capa["methods"][0u]["handler"] = "http"; - capa["methods"][0u]["type"] = "silverlight"; - capa["methods"][0u]["priority"] = 1; - } - - void OutHSS::sendNext(){ - if (thisPacket.getTime() >= playUntil){ - stop(); - wantRequest = true; - H.Chunkify("", 0, myConn); - H.Clean(); - return; - } - char *dataPointer = 0; - size_t len = 0; - thisPacket.getString("data", dataPointer, len); - H.Chunkify(dataPointer, len, myConn); - } - - int OutHSS::canSeekms(unsigned int ms){ - // no tracks? Frame too new by definition. - if (!myMeta.tracks.size()){ - DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns 1 because no tracks", ms); - return 1; - } - // loop trough all selected tracks - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - // return "too late" if one track is past this point - if (ms < myMeta.tracks[*it].firstms){ - DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns -1 because track %lu firstms == %llu", - ms, *it, myMeta.tracks[*it].firstms); - return -1; - } - // return "too early" if one track is not yet at this point - if (ms > myMeta.tracks[*it].lastms){ - DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns 1 because track %lu lastms == %llu", ms, - *it, myMeta.tracks[*it].lastms); - return 1; - } - } - return 0; - } - - void OutHSS::sendHeader(){ - // We have a non-manifest request, parse it. - std::string Quality = H.url.substr(H.url.find("Q(", 2) + 2); - Quality = Quality.substr(0, Quality.find(")")); - std::string parseString = H.url.substr(H.url.find(")/") + 2); - parseString = parseString.substr(parseString.find("(") + 1); - long long int seekTime = atoll(parseString.substr(0, parseString.find(")")).c_str()) / 10000; - unsigned int tid = atoll(Quality.c_str()) % 100; - selectedTracks.clear(); - selectedTracks.insert(tid); - if (myMeta.live){ - updateMeta(); - unsigned int timeout = 0; - int seekable; - do{ - seekable = canSeekms(seekTime); - if (seekable == 0){ - // iff the fragment in question is available, check if the next is available too - for (std::deque::iterator it = myMeta.tracks[tid].keys.begin(); - it != myMeta.tracks[tid].keys.end(); it++){ - if (it->getTime() >= seekTime){ - if ((it + 1) == myMeta.tracks[tid].keys.end()){seekable = 1;} - break; - } - } - } - if (seekable > 0){ - // time out after 21 seconds - if (++timeout > 42){ - myConn.close(); - break; - } - Util::wait(500); - updateMeta(); - } - }while (myConn && seekable > 0); - if (seekable < 0){ - H.Clean(); - H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be " - "served.\n"); - myConn.SendNow(H.BuildResponse("412", "Fragment out of range")); - H.Clean(); // clean for any possible next requests - std::cout << "Fragment @ " << seekTime << "ms too old (" << myMeta.tracks[tid].firstms - << " - " << myMeta.tracks[tid].lastms << " ms)" << std::endl; - stop(); - wantRequest = true; - return; - } - } - seek(seekTime); - ///\todo Rewrite to fragments - for (std::deque::iterator it2 = myMeta.tracks[tid].keys.begin(); - it2 != myMeta.tracks[tid].keys.end(); it2++){ - if (it2->getTime() > seekTime){ - playUntil = it2->getTime(); - break; - } - } - myTrackStor = tid; - myKeyStor = seekTime; - keysToSend = 1; - // Seek to the right place and send a play-once for a single fragment. - std::stringstream sstream; - - int partOffset = 0; - DTSC::Key keyObj; - for (std::deque::iterator it = myMeta.tracks[tid].keys.begin(); - it != myMeta.tracks[tid].keys.end(); it++){ - if (it->getTime() >= seekTime){ - keyObj = (*it); - std::deque::iterator nextIt = it; - nextIt++; - if (nextIt == myMeta.tracks[tid].keys.end()){ - if (myMeta.live){ - H.Clean(); - H.SetBody("Proxy, re-request this in a second or two.\n"); - myConn.SendNow(H.BuildResponse("208", "Ask again later")); - H.Clean(); // clean for any possible next requests - std::cout << "Fragment after fragment @ " << seekTime << " not available yet" << std::endl; - } - } - break; - } - partOffset += it->getParts(); - } - if (H.url == "/"){ - return; // Don't continue, but continue instead. - } - /* - if (myMeta.live){ - if (mstime == 0 && seekTime > 1){ - H.Clean(); - H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be - served.\n"); myConn.SendNow(H.BuildResponse("412", "Fragment out of range")); H.Clean(); //clean - for any possible next requests std::cout << "Fragment @ " << seekTime << " too old" << - std::endl; continue; - } - } - */ - - ///\todo Select correct track (tid); - - // Wrap everything in mp4 boxes - MP4::MFHD mfhd_box; - mfhd_box.setSequenceNumber(((keyObj.getNumber() - 1) * 2) + (myMeta.tracks[tid].type == "video" ? 1 : 2)); - - MP4::TFHD tfhd_box; - tfhd_box.setFlags(MP4::tfhdSampleFlag); - tfhd_box.setTrackID((myMeta.tracks[tid].type == "video" ? 1 : 2)); - if (myMeta.tracks[tid].type == "video"){ - tfhd_box.setDefaultSampleFlags(0x00004001); - }else{ - tfhd_box.setDefaultSampleFlags(0x00008002); - } - - MP4::TRUN trun_box; - trun_box.setDataOffset(42); ///\todo Check if this is a placeholder, or an actually correct number - unsigned int keySize = 0; - if (myMeta.tracks[tid].type == "video"){ - trun_box.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | - MP4::trunsampleSize | MP4::trunsampleOffsets); - }else{ - trun_box.setFlags(MP4::trundataOffset | MP4::trunsampleDuration | MP4::trunsampleSize); - } - trun_box.setFirstSampleFlags(0x00004002); - for (int i = 0; i < keyObj.getParts(); i++){ - MP4::trunSampleInformation trunSample; - trunSample.sampleSize = myMeta.tracks[tid].parts[i + partOffset].getSize(); - keySize += myMeta.tracks[tid].parts[i + partOffset].getSize(); - trunSample.sampleDuration = myMeta.tracks[tid].parts[i + partOffset].getDuration() * 10000; - if (myMeta.tracks[tid].type == "video"){ - trunSample.sampleOffset = myMeta.tracks[tid].parts[i + partOffset].getOffset() * 10000; - } - trun_box.setSampleInformation(trunSample, i); - } - - MP4::SDTP sdtp_box; - sdtp_box.setVersion(0); - if (myMeta.tracks[tid].type == "video"){ - sdtp_box.setValue(36, 4); - for (int i = 1; i < keyObj.getParts(); i++){sdtp_box.setValue(20, 4 + i);} - }else{ - sdtp_box.setValue(40, 4); - for (int i = 1; i < keyObj.getParts(); i++){sdtp_box.setValue(40, 4 + i);} - } - - MP4::TRAF traf_box; - traf_box.setContent(tfhd_box, 0); - traf_box.setContent(trun_box, 1); - traf_box.setContent(sdtp_box, 2); - - // If the stream is live, we want to have a fragref box if possible - //////HEREHEREHERE - if (myMeta.live){ - MP4::UUID_TFXD tfxd_box; - tfxd_box.setTime(keyObj.getTime()); - tfxd_box.setDuration(keyObj.getLength()); - traf_box.setContent(tfxd_box, 3); - - MP4::UUID_TrackFragmentReference fragref_box; - fragref_box.setVersion(1); - fragref_box.setFragmentCount(0); - int fragCount = 0; - for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++){ - if (myMeta.tracks[tid].keys[i].getTime() > seekTime){ - DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %llu > %lld", i, - myMeta.tracks[tid].keys[i].getTime(), seekTime); - fragref_box.setTime(fragCount, myMeta.tracks[tid].keys[i].getTime() * 10000); - fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000); - fragref_box.setFragmentCount(++fragCount); - } - } - traf_box.setContent(fragref_box, 4); - } - - MP4::MOOF moof_box; - moof_box.setContent(mfhd_box, 0); - moof_box.setContent(traf_box, 1); - /*LTS-START*/ - ///\TODO This encryption-handling section does not handle thisPacket correctly! - if (nProxy.encrypt){ - MP4::UUID_SampleEncryption sEnc; - sEnc.setVersion(0); - if (myMeta.tracks[tid].type == "audio"){ - sEnc.setFlags(0); - for (int i = 0; i < keyObj.getParts(); i++){ - MP4::UUID_SampleEncryption_Sample newSample; - prepareNext(); - thisPacket.getString("ivec", newSample.InitializationVector); - sEnc.setSample(newSample, i); - } - }else{ - sEnc.setFlags(2); - std::deque tmpParts; - for (int i = 0; i < keyObj.getParts(); i++){ - // Get the correct packet - prepareNext(); - MP4::UUID_SampleEncryption_Sample newSample; - thisPacket.getString("ivec", newSample.InitializationVector); - - std::deque nalSizes = nalu::parseNalSizes(thisPacket); - for (std::deque::iterator it = nalSizes.begin(); it != nalSizes.end(); it++){ - int encrypted = (*it - 5) & ~0xF; // Bitmask to a multiple of 16 - MP4::UUID_SampleEncryption_Sample_Entry newEntry; - newEntry.BytesClear = *it - encrypted; // Size + nal_unit_type - newEntry.BytesEncrypted = encrypted; // Entire NAL except nal_unit_type; - newSample.Entries.push_back(newEntry); - } - sEnc.setSample(newSample, i); - } - } - traf_box.setContent(sEnc, 3); - } - seek(seekTime); - /*LTS-END*/ - // Setting the correct offsets. - moof_box.setContent(traf_box, 1); - trun_box.setDataOffset(moof_box.boxedSize() + 8); - traf_box.setContent(trun_box, 1); - moof_box.setContent(traf_box, 1); - - H.Clean(); - H.SetHeader("Content-Type", "video/mp4"); - H.setCORSHeaders(); - H.StartResponse(H, myConn); - H.Chunkify(moof_box.asBox(), moof_box.boxedSize(), myConn); - int size = htonl(keySize + 8); - H.Chunkify((char *)&size, 4, myConn); - H.Chunkify("mdat", 4, myConn); - sentHeader = true; - H.Clean(); - } - - /*LTS-START*/ - void OutHSS::loadEncryption(){ - static bool encryptionLoaded = false; - if (!encryptionLoaded){ - // Load the encryption data page - char pageName[NAME_BUFFER_SIZE]; - snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_ENCRYPT, streamName.c_str()); - nProxy.encryptionPage.init(pageName, 8 * 1024 * 1024, false, false); - if (nProxy.encryptionPage.mapped){ - nProxy.vmData.read(nProxy.encryptionPage.mapped); - nProxy.encrypt = true; - } - encryptionLoaded = true; - } - } - - std::string OutHSS::protectionHeader(){ - loadEncryption(); - std::string xmlGen = - "16AESCTR"; - xmlGen += nProxy.vmData.keyid; - xmlGen += ""; - xmlGen += nProxy.vmData.laurl; - xmlGen += ""; - std::string tmp = toUTF16(xmlGen); - tmp = tmp.substr(2); - std::stringstream resGen; - resGen << (char)((tmp.size() + 10) & 0xFF); - resGen << (char)(((tmp.size() + 10) >> 8) & 0xFF); - resGen << (char)(((tmp.size() + 10) >> 16) & 0xFF); - resGen << (char)(((tmp.size() + 10) >> 24) & 0xFF); - resGen << (char)0x01 << (char)0x00; - resGen << (char)0x01 << (char)0x00; - resGen << (char)((tmp.size()) & 0xFF); - resGen << (char)(((tmp.size()) >> 8) & 0xFF); - resGen << tmp; - return Encodings::Base64::encode(resGen.str()); - } - /*LTS-END*/ - - ///\brief Builds an index file for HTTP Smooth streaming. - ///\param encParams The encryption parameters. /*LTS*/ - ///\return The index file for HTTP Smooth Streaming. - std::string OutHSS::smoothIndex(){ - loadEncryption(); // LTS - updateMeta(); - std::stringstream Result; - Result << "\n"; - Result << "::iterator> audioIters; - std::deque::iterator> videoIters; - long long int maxWidth = 0; - long long int maxHeight = 0; - long long int minWidth = 99999999; - long long int minHeight = 99999999; - for (std::map::iterator it = myMeta.tracks.begin(); - it != myMeta.tracks.end(); it++){ - if (it->second.codec == "AAC"){audioIters.push_back(it);} - if (it->second.codec == "H264"){ - videoIters.push_back(it); - if (it->second.width > maxWidth){maxWidth = it->second.width;} - if (it->second.width < minWidth){minWidth = it->second.width;} - if (it->second.height > maxHeight){maxHeight = it->second.height;} - if (it->second.height < minHeight){minHeight = it->second.height;} - } - } - DEBUG_MSG(DLVL_DONTEVEN, "Buffer window here %lld", myMeta.bufferWindow); - if (myMeta.vod){ - Result << "Duration=\"" - << ((*videoIters.begin())->second.lastms - (*videoIters.begin())->second.firstms) << "0000\""; - }else{ - Result << "Duration=\"0\" " - "IsLive=\"TRUE\" " - "LookAheadFragmentCount=\"2\" " - "DVRWindowLength=\"" - << myMeta.bufferWindow - << "0000\" " - "CanSeek=\"TRUE\" " - "CanPause=\"TRUE\" "; - } - Result << ">\n"; - - // Add audio entries - if (audioIters.size()){ - Result << "second.keys.size() - << "\" " - "Url=\"Q({bitrate})/A({start time})\">\n"; - int index = 0; - for (std::deque::iterator>::iterator it = audioIters.begin(); - it != audioIters.end(); it++){ - Result << "second.bps, (*it)->first) - << "\" " - "CodecPrivateData=\"" - << std::hex; - for (unsigned int i = 0; i < (*it)->second.init.size(); i++){ - Result << std::setfill('0') << std::setw(2) << std::right << (int)(*it)->second.init[i]; - } - Result << std::dec - << "\" " - "SamplingRate=\"" - << (*it)->second.rate - << "\" " - "Channels=\"2\" " - "BitsPerSample=\"16\" " - "PacketSize=\"4\" " - "AudioTag=\"255\" " - "FourCC=\"AACL\" >\n"; - Result << "\n"; - index++; - } - if ((*audioIters.begin())->second.keys.size()){ - for (std::deque::iterator it = (*audioIters.begin())->second.keys.begin(); - it != (((*audioIters.begin())->second.keys.end()) - 1); it++){ - Result << "second.keys.begin()){ - Result << "t=\"" << it->getTime() * 10000 << "\" "; - } - Result << "d=\"" << it->getLength() * 10000 << "\" />\n"; - } - } - Result << "\n"; - } - // Add video entries - if (videoIters.size()){ - Result << "second.keys.size() - << "\" " - "Url=\"Q({bitrate})/V({start time})\" " - "MaxWidth=\"" - << maxWidth - << "\" " - "MaxHeight=\"" - << maxHeight - << "\" " - "DisplayWidth=\"" - << maxWidth - << "\" " - "DisplayHeight=\"" - << maxHeight << "\">\n"; - int index = 0; - for (std::deque::iterator>::iterator it = videoIters.begin(); - it != videoIters.end(); it++){ - // Add video qualities - Result << "second.bps, (*it)->first) - << "\" " - "CodecPrivateData=\"" - << std::hex; - MP4::AVCC avccbox; - avccbox.setPayload((*it)->second.init); - std::string tmpString = avccbox.asAnnexB(); - for (unsigned int i = 0; i < tmpString.size(); i++){ - Result << std::setfill('0') << std::setw(2) << std::right << (int)tmpString[i]; - } - Result << std::dec - << "\" " - "MaxWidth=\"" - << (*it)->second.width - << "\" " - "MaxHeight=\"" - << (*it)->second.height - << "\" " - "FourCC=\"AVC1\" >\n"; - Result << "\n"; - index++; - } - if ((*videoIters.begin())->second.keys.size()){ - for (std::deque::iterator it = (*videoIters.begin())->second.keys.begin(); - it != (((*videoIters.begin())->second.keys.end()) - 1); it++){ - Result << "second.keys.begin()){ - Result << "t=\"" << it->getTime() * 10000 << "\" "; - } - Result << "d=\"" << it->getLength() * 10000 << "\" />\n"; - } - } - Result << "\n"; - } - /*LTS-START*/ - if (nProxy.encrypt){ - Result << ""; - Result << protectionHeader(); - Result << ""; - } - /*LTS-END*/ - Result << "\n"; - -#if DEBUG >= 8 - std::cerr << "Sending this manifest:" << std::endl << Result << std::endl; -#endif - return toUTF16(Result.str()); - }// smoothIndex - - void OutHSS::onHTTP(){ - if ((H.method == "OPTIONS" || H.method == "HEAD") && H.url.find("Manifest") == std::string::npos){ - H.Clean(); - H.SetHeader("Content-Type", "application/octet-stream"); - H.SetHeader("Cache-Control", "no-cache"); - H.setCORSHeaders(); - H.SendResponse("200", "OK", myConn); - H.Clean(); - return; - } - initialize(); - loadEncryption(); // LTS - if (H.url.find("Manifest") != std::string::npos){ - // Manifest, direct reply - H.Clean(); - H.SetHeader("Content-Type", "text/xml"); - H.SetHeader("Cache-Control", "no-cache"); - H.setCORSHeaders(); - if (H.method == "OPTIONS" || H.method == "HEAD"){ - H.SendResponse("200", "OK", myConn); - return; - } - std::string manifest = smoothIndex(); - H.SetBody(manifest); - H.SendResponse("200", "OK", myConn); - H.Clean(); - }else{ - parseData = true; - wantRequest = false; - sendHeader(); - } - } -}// namespace Mist diff --git a/src/output/output_hss.h b/src/output/output_hss.h deleted file mode 100644 index 7a00d904..00000000 --- a/src/output/output_hss.h +++ /dev/null @@ -1,26 +0,0 @@ -#include "output_http.h" -#include - -namespace Mist{ - class OutHSS : public HTTPOutput{ - public: - OutHSS(Socket::Connection &conn); - ~OutHSS(); - static void init(Util::Config *cfg); - void onHTTP(); - void sendNext(); - void sendHeader(); - - protected: - std::string protectionHeader(); /*LTS*/ - std::string smoothIndex(); - void loadEncryption(); /*LTS*/ - int canSeekms(unsigned int ms); - int keysToSend; - int myTrackStor; - int myKeyStor; - unsigned long long playUntil; - }; -}// namespace Mist - -typedef Mist::OutHSS mistOut; diff --git a/src/output/output_http.cpp b/src/output/output_http.cpp index d4577943..c4026bd7 100644 --- a/src/output/output_http.cpp +++ b/src/output/output_http.cpp @@ -223,8 +223,8 @@ namespace Mist{ MEDIUM_MSG("Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(), streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str()); streamName = H.GetVar("stream"); - nProxy.userClient.finish(); - statsPage.finish(); + userSelect.clear(); + if (statComm){statComm.setStatus(COMM_STATUS_DISCONNECT);} reConnector(handler); onFail("Server error - could not start connector", true); return; @@ -285,7 +285,6 @@ namespace Mist{ webSock = 0; return; } - crc = getpid(); onWebsocketConnect(); H.Clean(); return; @@ -388,7 +387,6 @@ namespace Mist{ DTSC::Scan capa = rCapa.getMember("connectors"); pipedCapa = capa.getMember(connector).asJSON(); } - // build arguments for starting output process std::string tmparg = Util::getMyPath() + std::string("MistOut") + connector; std::string tmpPrequest; diff --git a/src/output/output_http_internal.cpp b/src/output/output_http_internal.cpp index 0972ce6c..ab581345 100644 --- a/src/output/output_http_internal.cpp +++ b/src/output/output_http_internal.cpp @@ -110,7 +110,6 @@ namespace Mist{ capa["desc"] = "HTTP connection handler, provides all enabled HTTP-based outputs"; capa["provides"] = "HTTP"; capa["protocol"] = "http://"; - capa["codecs"][0u][0u].append("*"); capa["url_rel"] = "/$.html"; capa["url_match"].append("/crossdomain.xml"); capa["url_match"].append("/clientaccesspolicy.xml"); @@ -237,6 +236,15 @@ namespace Mist{ } } } + bool allowBFrames = true; + if (conncapa.isMember("methods")){ + jsonForEach(conncapa["methods"], mthd){ + if (mthd->isMember("nobframes") && (*mthd)["nobframes"]){ + allowBFrames = false; + break; + } + } + } const std::string &rel = conncapa["url_rel"].asStringRef(); unsigned int most_simul = 0; unsigned int total_matches = 0; @@ -250,31 +258,31 @@ namespace Mist{ jsonForEach((*itb), itc){ const std::string &strRef = (*itc).asStringRef(); bool byType = false; - bool multiSel = false; uint8_t shift = 0; if (strRef[shift] == '@'){ byType = true; ++shift; } if (strRef[shift] == '+'){ - multiSel = true; ++shift; } jsonForEach(strmMeta["tracks"], trit){ if ((!byType && (*trit)["codec"].asStringRef() == strRef.substr(shift)) || (byType && (*trit)["type"].asStringRef() == strRef.substr(shift)) || strRef.substr(shift) == "*"){ - matches++; - total_matches++; - if (conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() && - conncapa["exceptions"].size()){ - jsonForEach(conncapa["exceptions"], ex){ - if (ex.key() == "codec:" + strRef.substr(shift)){ - if (!Util::checkException(*ex, useragent)){ - matches--; - total_matches--; + if (allowBFrames || !(trit->isMember("bframes") && (*trit)["bframes"])){ + matches++; + total_matches++; + if (conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() && + conncapa["exceptions"].size()){ + jsonForEach(conncapa["exceptions"], ex){ + if (ex.key() == "codec:" + strRef.substr(shift)){ + if (!Util::checkException(*ex, useragent)){ + matches--; + total_matches--; + } + break; } - break; } } } @@ -392,9 +400,13 @@ namespace Mist{ "'),MistVideoObject:mv" + forceType + devSkin + "});" + seekTo + ""); if ((uAgent.find("iPad") != std::string::npos) || (uAgent.find("iPod") != std::string::npos) || (uAgent.find("iPhone") != std::string::npos)){ - H.SetHeader("Location", hlsUrl); - H.SendResponse("307", "HLS redirect", myConn); - return; + if (uAgent.find("OS 11") == std::string::npos && uAgent.find("OS 12") == std::string::npos && + uAgent.find("OS 13") == std::string::npos && uAgent.find("OS 14") == std::string::npos && + uAgent.find("OS 15") == std::string::npos && uAgent.find("OS 16") == std::string::npos){ + H.SetHeader("Location", hlsUrl); + H.SendResponse("307", "HLS redirect", myConn); + return; + } } H.SendResponse("200", "OK", myConn); } @@ -450,39 +462,32 @@ namespace Mist{ if (!myConn){return json_resp;} bool hasVideo = false; - for (std::map::iterator trit = myMeta.tracks.begin(); - trit != myMeta.tracks.end(); trit++){ - if (trit->second.type == "video"){ + std::set validTracks = M.getValidTracks(); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (M.getType(*it) == "video"){ hasVideo = true; - if (trit->second.width > json_resp["width"].asInt()){ - json_resp["width"] = trit->second.width; - } - if (trit->second.height > json_resp["height"].asInt()){ - json_resp["height"] = trit->second.height; + if (M.getWidth(*it) > json_resp["width"].asInt()){json_resp["width"] = M.getWidth(*it);} + if (M.getHeight(*it) > json_resp["height"].asInt()){ + json_resp["height"] = M.getHeight(*it); } } } if (json_resp["width"].asInt() < 1 || json_resp["height"].asInt() < 1){ json_resp["width"] = 640; - json_resp["height"] = 480; - if (!hasVideo){json_resp["height"] = 20;} + json_resp["height"] = (hasVideo ? 480 : 20); } - if (myMeta.vod){json_resp["type"] = "vod";} - if (myMeta.live){json_resp["type"] = "live";} + json_resp["type"] = (M.getVod() ? "vod" : "live"); // show ALL the meta datas! - json_resp["meta"] = myMeta.toJSON(); + M.toJSON(json_resp["meta"], true); jsonForEach(json_resp["meta"]["tracks"], it){ if (it->isMember("lang")){ (*it)["language"] = Encodings::ISO639::decode((*it)["lang"].asStringRef()); } - it->removeMember("fragments"); - it->removeMember("keys"); - it->removeMember("keysizes"); - it->removeMember("parts"); - it->removeMember("ivecs"); /*LTS*/ + if (M.hasBFrames((*it)["idx"].asInt())){(*it)["bframes"] = 1;} } json_resp["meta"].removeMember("source"); + json_resp["meta"]["bframes"] = (M.hasBFrames() ? 1 : 0); // Get sources/protocols information Util::DTSCShmReader rCapa(SHM_CAPA); @@ -704,14 +709,13 @@ namespace Mist{ initialize(); if (!myConn){return;} - for (std::map::iterator trit = myMeta.tracks.begin(); - trit != myMeta.tracks.end(); trit++){ - if (trit->second.type == "video"){ - trackSources += "