From 4bdbd82f66070d16f54777265fc907bfb42c6d22 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 5 Apr 2015 21:38:36 +0200 Subject: [PATCH] LTS Commits --- CMakeLists.txt | 46 +- Makefile | 124 ++++- lib/dtsc.cpp | 55 +- lib/dtsc.h | 26 + lib/dtscmeta.cpp | 74 +++ lib/encryption.cpp | 62 +++ lib/encryption.h | 8 + lib/mp4.cpp | 61 ++- lib/mp4_dash.cpp | 240 ++++++++ lib/mp4_dash.h | 73 +++ lib/mp4_encryption.cpp | 456 +++++++++++++++ lib/mp4_encryption.h | 104 ++++ lib/mp4_generic.cpp | 449 +++++++++++++-- lib/mp4_generic.h | 107 +++- lib/mp4_ms.cpp | 12 + lib/rtp.cpp | 239 ++++++++ lib/rtp.h | 62 +++ lib/stream.cpp | 13 + lib/tinythread.cpp | 8 +- lib/ts_packet.cpp | 4 + src/analysers/info.cpp | 1 + src/analysers/mp4_analyser.cpp | 7 + src/analysers/rtp_analyser.cpp | 209 +++++++ src/analysers/rtsp_rtp_analyser.cpp | 197 +++++++ src/analysers/stats_analyser.cpp | 84 +++ src/analysers/ts_analyser.cpp | 189 +++++++ src/controller/controller.cpp | 46 ++ src/controller/controller_api.cpp | 177 ++++++ src/controller/controller_limits.cpp | 403 ++++++++++++++ src/controller/controller_limits.h | 21 + src/controller/controller_statistics.cpp | 54 ++ src/controller/controller_statistics.h | 5 + src/controller/controller_streams.cpp | 25 +- src/controller/controller_updater.cpp | 253 +++++++++ src/controller/controller_updater.h | 21 + src/controller/controller_uplink.cpp | 138 +++++ src/controller/controller_uplink.h | 3 + src/input/input_av.cpp | 281 ++++++++++ src/input/input_av.h | 32 ++ src/input/input_buffer.cpp | 244 ++++++++- src/input/input_buffer.h | 9 + src/input/input_folder.cpp | 19 + src/input/input_folder.h | 14 + src/input/input_ismv.cpp | 407 ++++++++++++++ src/input/input_ismv.h | 51 ++ src/input/input_mp3.cpp | 2 +- src/input/input_mp4.cpp | 632 +++++++++++++++++++++ src/input/input_mp4.h | 102 ++++ src/input/input_ts.cpp | 516 +++++++++++++++++ src/input/input_ts.h | 81 +++ src/input/mist_in_folder.cpp | 53 ++ src/output/mist_out.cpp | 29 + src/output/output.cpp | 284 ++++++++++ src/output/output.h | 28 +- src/output/output_dash_mp4.cpp | 669 +++++++++++++++++++++++ src/output/output_dash_mp4.h | 33 ++ src/output/output_hds.cpp | 14 + src/output/output_hls.cpp | 28 +- src/output/output_hss.cpp | 83 ++- src/output/output_hss.h | 4 + src/output/output_http_internal.cpp | 10 + src/output/output_httpts.cpp | 2 + src/output/output_progressive_mp4.cpp | 50 ++ src/output/output_progressive_mp4.h | 3 + src/output/output_rtmp.cpp | 20 +- src/output/output_rtsp.cpp | 406 ++++++++++++++ src/output/output_rtsp.h | 47 ++ src/output/output_ts.cpp | 2 + src/output/output_ts_base.cpp | 18 + src/output/output_ts_base.h | 4 + src/output/output_ts_push.cpp | 99 ++++ src/output/output_ts_push.h | 18 + 72 files changed, 8245 insertions(+), 105 deletions(-) create mode 100644 lib/encryption.cpp create mode 100644 lib/encryption.h create mode 100644 lib/mp4_dash.cpp create mode 100644 lib/mp4_dash.h create mode 100644 lib/mp4_encryption.cpp create mode 100644 lib/mp4_encryption.h create mode 100644 lib/rtp.cpp create mode 100644 lib/rtp.h create mode 100644 src/analysers/rtp_analyser.cpp create mode 100644 src/analysers/rtsp_rtp_analyser.cpp create mode 100644 src/analysers/stats_analyser.cpp create mode 100755 src/analysers/ts_analyser.cpp create mode 100644 src/controller/controller_limits.cpp create mode 100644 src/controller/controller_limits.h create mode 100644 src/controller/controller_updater.cpp create mode 100644 src/controller/controller_updater.h create mode 100644 src/controller/controller_uplink.cpp create mode 100644 src/controller/controller_uplink.h create mode 100644 src/input/input_av.cpp create mode 100644 src/input/input_av.h create mode 100644 src/input/input_folder.cpp create mode 100644 src/input/input_folder.h create mode 100644 src/input/input_ismv.cpp create mode 100644 src/input/input_ismv.h create mode 100644 src/input/input_mp4.cpp create mode 100644 src/input/input_mp4.h create mode 100755 src/input/input_ts.cpp create mode 100755 src/input/input_ts.h create mode 100644 src/input/mist_in_folder.cpp create mode 100644 src/output/output_dash_mp4.cpp create mode 100644 src/output/output_dash_mp4.h create mode 100644 src/output/output_rtsp.cpp create mode 100644 src/output/output_rtsp.h create mode 100644 src/output/output_ts_push.cpp create mode 100644 src/output/output_ts_push.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e46c222..5aaba37c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,24 @@ if (NOT DEFINED ${NOSHM} ) add_definitions(-DSHM_ENABLED=1) endif() + +if (NOT DEFINED FILLER_DATA OR NOT DEFINED SHARED_SECRET OR NOT DEFINED SUPER_SECRET)#LTS + message(WARNING "Not all LTS variables have been set and this is an LTS build - are you sure about this?")#LTS +endif()#LTS +add_definitions(-DFILLER_DATA="${FILLER_DATA}" -DSHARED_SECRET="${SHARED_SECRET}" -DSUPER_SECRET="${SUPER_SECRET}")#LTS +if (DEFINED ${GEOIP} ) + add_definitions(-DGEOIP=1) +endif() +if (NOT DEFINED ${NOUPDATE} ) + add_definitions(-DUPDATER=1) +endif() +if (DEFINED ${NOAUTH} ) + add_definitions(-DNOAUTH=1) +endif() +if (DEFINED ${KILLONEXIT} ) + add_definitions(-DKILLONEXIT=true) +endif() + ######################################## # Build Variables - Thread Names # ######################################## @@ -95,12 +113,16 @@ set(libHeaders ${SOURCE_DIR}/lib/converter.h ${SOURCE_DIR}/lib/defines.h ${SOURCE_DIR}/lib/dtsc.h + ${SOURCE_DIR}/lib/encryption.h ${SOURCE_DIR}/lib/filesystem.h ${SOURCE_DIR}/lib/flv_tag.h ${SOURCE_DIR}/lib/ftp.h ${SOURCE_DIR}/lib/http_parser.h ${SOURCE_DIR}/lib/json.h ${SOURCE_DIR}/lib/mp4_adobe.h + ${SOURCE_DIR}/lib/mp4_dash.cpp + ${SOURCE_DIR}/lib/mp4_dash.h + ${SOURCE_DIR}/lib/mp4_encryption.h ${SOURCE_DIR}/lib/mp4_generic.h ${SOURCE_DIR}/lib/mp4.h ${SOURCE_DIR}/lib/mp4_ms.h @@ -108,6 +130,7 @@ set(libHeaders ${SOURCE_DIR}/lib/ogg.h ${SOURCE_DIR}/lib/procs.h ${SOURCE_DIR}/lib/rtmpchunks.h + ${SOURCE_DIR}/lib/rtp.h ${SOURCE_DIR}/lib/shared_memory.h ${SOURCE_DIR}/lib/socket.h ${SOURCE_DIR}/lib/stream.h @@ -131,6 +154,7 @@ set(libSources ${SOURCE_DIR}/lib/converter.cpp ${SOURCE_DIR}/lib/dtsc.cpp ${SOURCE_DIR}/lib/dtscmeta.cpp + ${SOURCE_DIR}/lib/encryption.cpp ${SOURCE_DIR}/lib/filesystem.cpp ${SOURCE_DIR}/lib/flv_tag.cpp ${SOURCE_DIR}/lib/ftp.cpp @@ -138,12 +162,15 @@ set(libSources ${SOURCE_DIR}/lib/json.cpp ${SOURCE_DIR}/lib/mp4_adobe.cpp ${SOURCE_DIR}/lib/mp4.cpp + ${SOURCE_DIR}/lib/mp4_dash.cpp + ${SOURCE_DIR}/lib/mp4_encryption.cpp ${SOURCE_DIR}/lib/mp4_generic.cpp ${SOURCE_DIR}/lib/mp4_ms.cpp ${SOURCE_DIR}/lib/nal.cpp ${SOURCE_DIR}/lib/ogg.cpp ${SOURCE_DIR}/lib/procs.cpp ${SOURCE_DIR}/lib/rtmpchunks.cpp + ${SOURCE_DIR}/lib/rtp.cpp ${SOURCE_DIR}/lib/shared_memory.cpp ${SOURCE_DIR}/lib/socket.cpp ${SOURCE_DIR}/lib/stream.cpp @@ -153,7 +180,6 @@ set(libSources ${SOURCE_DIR}/lib/ts_packet.cpp ${SOURCE_DIR}/lib/vorbis.cpp ) - ######################################## # MistLib - Build # ######################################## @@ -206,6 +232,9 @@ makeAnalyser(DTSC dtsc) makeAnalyser(AMF amf) makeAnalyser(MP4 mp4) makeAnalyser(OGG ogg) +makeAnalyser(RTP rtp) #LTS +makeAnalyser(RTSP rtsp_rtp) #LTS +makeAnalyser(Stats stats) #LTS add_executable(MistInfo src/analysers/info.cpp ) @@ -244,6 +273,10 @@ makeInput(MP3 mp3) makeInput(FLV flv) makeInput(OGG ogg) makeInput(Buffer buffer) +makeInput(ISMV ismv)#LTS +makeInput(MP4 mp4)#LTS +makeInput(TS ts)#LTS +makeInput(Folder folder)#LTS ######################################## # MistServer - Outputs # @@ -292,6 +325,10 @@ makeOutput(JSON json http) makeOutput(TS ts ts) makeOutput(HTTPTS httpts http ts) makeOutput(HLS hls http ts) +makeOutput(RTSP rtsp)#LTS +makeOutput(TSPush ts_push ts)#LTS +makeOutput(DASH dash_mp4 http)#LTS + add_executable(MistOutHTTP src/output/mist_out.cpp src/output/output.cpp @@ -388,10 +425,13 @@ add_custom_target(localSettingsPage # MistController - Header Files # ######################################## set(controllerHeaders + ${SOURCE_DIR}/src/controller/controller_limits.h + ${SOURCE_DIR}/src/controller/controller_uplink.h ${SOURCE_DIR}/src/controller/controller_api.h ${SOURCE_DIR}/src/controller/controller_statistics.h ${SOURCE_DIR}/src/controller/controller_connectors.h ${SOURCE_DIR}/src/controller/controller_storage.h + ${SOURCE_DIR}/src/controller/controller_updater.h ${SOURCE_DIR}/src/controller/controller_capabilities.h ${SOURCE_DIR}/src/controller/controller_streams.h ) @@ -401,14 +441,16 @@ set(controllerHeaders ######################################## set(controllerSources ${SOURCE_DIR}/src/controller/controller.cpp + ${SOURCE_DIR}/src/controller/controller_updater.cpp ${SOURCE_DIR}/src/controller/controller_streams.cpp ${SOURCE_DIR}/src/controller/controller_storage.cpp ${SOURCE_DIR}/src/controller/controller_connectors.cpp ${SOURCE_DIR}/src/controller/controller_statistics.cpp + ${SOURCE_DIR}/src/controller/controller_limits.cpp ${SOURCE_DIR}/src/controller/controller_capabilities.cpp + ${SOURCE_DIR}/src/controller/controller_uplink.cpp ${SOURCE_DIR}/src/controller/controller_api.cpp ) - ######################################## # MistController - Build # ######################################## diff --git a/Makefile b/Makefile index 312834a3..6e4e8211 100644 --- a/Makefile +++ b/Makefile @@ -7,13 +7,30 @@ libdir = $(exec_prefix)/lib PACKAGE_VERSION := $(shell git describe --tags 2> /dev/null || cat VERSION 2> /dev/null || echo "Unknown") DEBUG = 4 RELEASE = Generic_$(shell getconf LONG_BIT) +GEOIP= # /*LTS*/ ifeq ($(PACKAGE_VERSION),Unknown) $(warning Version is unknown - consider creating a VERSION file or fixing your git setup.) endif CPPFLAGS = -Wall -g -O2 -fPIC -override CPPFLAGS += -funsigned-char -DDEBUG="$(DEBUG)" -DPACKAGE_VERSION="\"$(PACKAGE_VERSION)\"" -DRELEASE="\"$(RELEASE)\"" +override CPPFLAGS += -funsigned-char -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DDEBUG="$(DEBUG)" -DPACKAGE_VERSION="\"$(PACKAGE_VERSION)\"" -DRELEASE="\"$(RELEASE)\"" + +ifdef GEOIP +override CPPFLAGS += -DGEOIP=1 +endif + +ifndef NOUPDATE +override CPPFLAGS += -DUPDATER=1 +endif + +ifdef NOAUTH +override CPPFLAGS += -DNOAUTH=1 +endif + +ifdef KILLONEXIT +override CPPFLAGS += -DKILL_ON_EXIT=true +endif ifndef NOSHM override CPPFLAGS += -DSHM_ENABLED=1 @@ -27,6 +44,21 @@ THREADLIB = -lpthread -lrt LDLIBS = ${THREADLIB} LDFLAGS = -I${includedir} -L${libdir} -lmist +# /*LTS-START*/ +FILLER_DATA = +SHARED_SECRET = +SUPER_SECRET = +override CPPFLAGS += -DFILLER_DATA=\"$(FILLER_DATA)\" -DSHARED_SECRET=\"$(SHARED_SECRET)\" -DSUPER_SECRET=\"$(SUPER_SECRET)\" +ifeq ($(FILLER_DATA),) + $(warning Filler data is empty and this is an LTS build - did you set FILLER_DATA?) +endif +ifeq ($(SHARED_SECRET),) + $(warning Shared secret is empty and this is an LTS build - did you set SHARED_SECRET?) +endif +ifeq ($(SUPER_SECRET),) + $(warning Super secret is empty and this is an LTS build - did you set SUPER_SECRET?) +endif +# /*LTS-END*/ .DEFAULT_GOAL := all @@ -42,12 +74,29 @@ endif controller: MistController MistController: override LDLIBS += $(THREADLIB) +MistController: override LDLIBS += $(GEOIP) # /*LTS*/ MistController: src/controller/server.html.h src/controller/* $(CXX) $(LDFLAGS) $(CPPFLAGS) src/controller/*.cpp $(LDLIBS) -o $@ analysers: MistAnalyserRTMP MistAnalyserRTMP: src/analysers/rtmp_analyser.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + +analysers: MistAnalyserRTP +MistAnalyserRTP: src/analysers/rtp_analyser.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + +analysers: MistAnalyserTS +MistAnalyserTS: src/analysers/ts_analyser.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + +analysers: MistAnalyserRTSP +MistAnalyserRTSP: src/analysers/rtsp_rtp_analyser.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + +analysers: MistAnalyserStats +MistAnalyserStats: src/analysers/stats_analyser.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ analysers: MistAnalyserFLV MistAnalyserFLV: src/analysers/flv_analyser.cpp @@ -79,6 +128,12 @@ MistInDTSC: override CPPFLAGS += "-DINPUTTYPE=\"input_dtsc.h\"" MistInDTSC: src/input/mist_in.cpp src/input/input.cpp src/input/input_dtsc.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ +inputs: MistInISMV +MistInISMV: override LDLIBS += $(THREADLIB) +MistInISMV: override CPPFLAGS += "-DINPUTTYPE=\"input_ismv.h\"" +MistInISMV: src/input/mist_in.cpp src/input/input.cpp src/input/input_ismv.cpp src/io.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + inputs: MistInMP3 MistInMP3: override LDLIBS += $(THREADLIB) MistInMP3: override CPPFLAGS += "-DINPUTTYPE=\"input_mp3.h\"" @@ -91,6 +146,18 @@ MistInFLV: override CPPFLAGS += "-DINPUTTYPE=\"input_flv.h\"" MistInFLV: src/input/mist_in.cpp src/input/input.cpp src/input/input_flv.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ +inputs: MistInTS +MistInTS: override LDLIBS += $(THREADLIB) +MistInTS: override CPPFLAGS += "-DINPUTTYPE=\"input_ts.h\"" +MistInTS: src/input/mist_in.cpp src/input/input.cpp src/input/input_ts.cpp src/io.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + +inputs: MistInMP4 +MistInMP4: override LDLIBS += $(THREADLIB) +MistInMP4: override CPPFLAGS += "-DINPUTTYPE=\"input_mp4.h\"" +MistInMP4: src/input/mist_in.cpp src/input/input.cpp src/input/input_mp4.cpp src/io.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + inputs: MistInOGG MistInOGG: override LDLIBS += $(THREADLIB) MistInOGG: override CPPFLAGS += "-DINPUTTYPE=\"input_ogg.h\"" @@ -103,11 +170,32 @@ MistInBuffer: override CPPFLAGS += "-DINPUTTYPE=\"input_buffer.h\"" MistInBuffer: src/input/mist_in.cpp src/input/input.cpp src/input/input_buffer.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ +inputs: MistInFolder +MistInFolder: override LDLIBS += $(THREADLIB) +MistInFolder: override CPPFLAGS += "-DINPUTTYPE=\"input_folder.h\"" +MistInFolder: src/input/mist_in_folder.cpp src/input/input.cpp src/input/input_folder.cpp src/io.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + +MistInAV: override LDLIBS += -lavformat -lavcodec -lavutil $(THREADLIB) +MistInAV: override CPPFLAGS += "-DINPUTTYPE=\"input_av.h\"" +MistInAV: src/input/mist_in.cpp src/input/input.cpp src/input/input_av.cpp src/io.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) -D__STDC_CONSTANT_MACROS=1 $^ $(LDLIBS) -o $@ + outputs: MistOutFLV MistOutFLV: override LDLIBS += $(THREADLIB) +MistOutFLV: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutFLV: override CPPFLAGS += "-DOUTPUTTYPE=\"output_progressive_flv.h\"" MistOutFLV: src/output/mist_out.cpp src/output/output_http.cpp src/output/output.cpp src/output/output_progressive_flv.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + +# /*LTS-START*/ +outputs: MistOutRTSP +MistOutRTSP: override LDLIBS += $(THREADLIB) +MistOutRTSP: override LDLIBS += $(GEOIP) +MistOutRTSP: override CPPFLAGS += "-DOUTPUTTYPE=\"output_rtsp.h\"" +MistOutRTSP: src/output/mist_out.cpp src/output/output.cpp src/output/output_rtsp.cpp src/io.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ +# /*LTS-END*/ outputs: MistOutOGG MistOutOGG: override LDLIBS += $(THREADLIB) @@ -118,72 +206,98 @@ MistOutOGG: src/output/mist_out.cpp src/output/output_http.cpp src/output/output outputs: MistOutMP4 MistOutMP4: override LDLIBS += $(THREADLIB) +MistOutMP4: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutMP4: override CPPFLAGS += "-DOUTPUTTYPE=\"output_progressive_mp4.h\"" MistOutMP4: src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_progressive_mp4.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ outputs: MistOutMP3 MistOutMP3: override LDLIBS += $(THREADLIB) +MistOutMP3: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutMP3: override CPPFLAGS += "-DOUTPUTTYPE=\"output_progressive_mp3.h\"" MistOutMP3: src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_progressive_mp3.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ outputs: MistOutRTMP MistOutRTMP: override LDLIBS += $(THREADLIB) +MistOutRTMP: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutRTMP: override CPPFLAGS += "-DOUTPUTTYPE=\"output_rtmp.h\"" MistOutRTMP: src/output/mist_out.cpp src/output/output.cpp src/output/output_rtmp.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ outputs: MistOutRaw MistOutRaw: override LDLIBS += $(THREADLIB) +MistOutRaw: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutRaw: override CPPFLAGS += "-DOUTPUTTYPE=\"output_raw.h\"" MistOutRaw: src/output/mist_out.cpp src/output/output.cpp src/output/output_raw.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ outputs: MistOutHTTPTS MistOutHTTPTS: override LDLIBS += $(THREADLIB) +MistOutHTTPTS: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutHTTPTS: override CPPFLAGS += -DOUTPUTTYPE=\"output_httpts.h\" -DTS_BASECLASS=HTTPOutput MistOutHTTPTS: src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_httpts.cpp src/output/output_ts_base.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ outputs: MistOutTS MistOutTS: override LDLIBS += $(THREADLIB) +MistOutTS: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutTS: override CPPFLAGS += "-DOUTPUTTYPE=\"output_ts.h\"" MistOutTS: src/output/mist_out.cpp src/output/output.cpp src/output/output_ts.cpp src/output/output_ts_base.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ +outputs: MistOutTSPush +MistOutTSPush: override LDLIBS += $(THREADLIB) +MistOutTSPush: override LDLIBS += $(GEOIP) # /*LTS*/ +MistOutTSPush: override CPPFLAGS += "-DOUTPUTTYPE=\"output_ts_push.h\"" +MistOutTSPush: src/output/mist_out.cpp src/output/output.cpp src/output/output_ts_push.cpp src/output/output_ts_base.cpp src/io.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + outputs: MistOutHTTP MistOutHTTP: override LDLIBS += $(THREADLIB) +MistOutHTTP: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutHTTP: override CPPFLAGS += "-DOUTPUTTYPE=\"output_http_internal.h\"" MistOutHTTP: src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_http_internal.cpp src/embed.js.h src/io.cpp - $(CXX) $(LDFLAGS) $(CPPFLAGS) src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_http_internal.cpp $(LDLIBS) -o $@ + $(CXX) $(LDFLAGS) $(CPPFLAGS) src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_http_internal.cpp src/io.cpp $(LDLIBS) -o $@ outputs: MistOutHSS MistOutHSS: override LDLIBS += $(THREADLIB) +MistOutHSS: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutHSS: override CPPFLAGS += "-DOUTPUTTYPE=\"output_hss.h\"" MistOutHSS: src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_hss.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ outputs: MistOutHLS MistOutHLS: override LDLIBS += $(THREADLIB) +MistOutHLS: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutHLS: override CPPFLAGS += -DOUTPUTTYPE=\"output_hls.h\" -DTS_BASECLASS=HTTPOutput MistOutHLS: src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_hls.cpp src/output/output_ts_base.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ outputs: MistOutHDS MistOutHDS: override LDLIBS += $(THREADLIB) +MistOutHDS: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutHDS: override CPPFLAGS += "-DOUTPUTTYPE=\"output_hds.h\"" MistOutHDS: src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_hds.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + +outputs: MistOutDASHMP4 +MistOutDASHMP4: override LDLIBS += $(THREADLIB) +MistOutDASHMP4: override LDLIBS += $(GEOIP) # /*LTS*/ +MistOutDASHMP4: override CPPFLAGS += "-DOUTPUTTYPE=\"output_dash_mp4.h\"" +MistOutDASHMP4: src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_dash_mp4.cpp src/io.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ outputs: MistOutSRT MistOutSRT: override LDLIBS += $(THREADLIB) +MistOutSRT: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutSRT: override CPPFLAGS += "-DOUTPUTTYPE=\"output_srt.h\"" MistOutSRT: src/output/mist_out.cpp src/output/output_http.cpp src/output/output.cpp src/output/output_srt.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ outputs: MistOutJSON MistOutJSON: override LDLIBS += $(THREADLIB) +MistOutJSON: override LDLIBS += $(GEOIP) # /*LTS*/ MistOutJSON: override CPPFLAGS += "-DOUTPUTTYPE=\"output_json.h\"" MistOutJSON: src/output/mist_out.cpp src/output/output.cpp src/output/output_http.cpp src/output/output_json.cpp src/io.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ @@ -203,9 +317,9 @@ endif sourcery: src/sourcery.cpp $(CXX) -o $@ $(CPPFLAGS) $^ -src/embed.js.h: src/embed.js sourcery +src/output/embed.js.h: src/embed.js sourcery $(CLOSURE) src/embed.js > embed.min.js - ./sourcery embed.min.js embed_js > src/embed.js.h + ./sourcery embed.min.js embed_js src/output/embed.js.h rm embed.min.js src/controller/server.html: $(lspDATA) $(lspSOURCES) $(lspSOURCESmin) @@ -219,7 +333,7 @@ src/controller/server.html: $(lspDATA) $(lspSOURCES) $(lspSOURCESmin) cat lsp/footer.html >> $@ src/controller/server.html.h: src/controller/server.html sourcery - cd src/controller; ../../sourcery server.html server_html > server.html.h + cd src/controller; ../../sourcery server.html server_html server.html.h lib_objects := $(patsubst %.cpp,%.o,$(wildcard lib/*.cpp)) diff --git a/lib/dtsc.cpp b/lib/dtsc.cpp index 01269cce..05047343 100644 --- a/lib/dtsc.cpp +++ b/lib/dtsc.cpp @@ -356,6 +356,11 @@ void DTSC::Stream::cutOneBuffer() { metadata.tracks[trid].fragments.clear(); } } + /*LTS-START*/ + if (!recordPath.empty()) { + recordPacket(buffers.begin()->second); + } + /*LTS-END*/ deletionCallback(buffers.begin()->first); buffers.erase(buffers.begin()); } @@ -453,6 +458,35 @@ DTSC::Ring * DTSC::Stream::getRing() { return new DTSC::Ring(tmp); } +/*LTS-START*/ +/// Sets the recording path and writes the file header in preperation for the recording. +/// If the file cannot be opened the path is assumed to be invalid and an error is written. +void DTSC::Stream::setRecord(std::string path) { + if (path.empty()) { + return; + } + recordFile = new File(path, true); + if (!recordFile) { + DEBUG_MSG(DLVL_ERROR, "Failed to create file: %s", path.c_str()); + } + headerRecorded = false; + recordPath = path; +} +/*LTS-END*/ + +/*LTS-START*/ +/// Writes a packet to file, if the header was not yet written it writes that first. +void DTSC::Stream::recordPacket(JSON::Value & packet) { + if (!headerRecorded) { + metadata.moreheader = 0; + std::string header = metadata.toJSON().toPacked(); + recordFile->writeHeader(header, true); + headerRecorded = true; + } + recordFile->writePacket(packet); +} +/*LTS-END*/ + /// Deletes a given out Ring class from memory and internal Ring list. /// Checks for NULL pointers and invalid pointers, silently discarding them. void DTSC::Stream::dropRing(DTSC::Ring * ptr) { @@ -764,16 +798,13 @@ void DTSC::File::seekNext() { myPack.null(); return; } - fseek(F, currentPositions.begin()->bytePos, SEEK_SET); + seekPos thisPos = *currentPositions.begin(); + fseek(F, thisPos.bytePos, SEEK_SET); if (reachedEOF()) { myPack.null(); return; } clearerr(F); - if (!metadata.merged) { - seek_time(currentPositions.begin()->seekTime + 1, currentPositions.begin()->trackID); - fseek(F, currentPositions.begin()->bytePos, SEEK_SET); - } currentPositions.erase(currentPositions.begin()); lastreadpos = ftell(F); if (fread(buffer, 4, 1, F) != 1) { @@ -786,7 +817,7 @@ void DTSC::File::seekNext() { return; } if (memcmp(buffer, DTSC::Magic_Header, 4) == 0) { - seek_time(myPack.getTime() + 1, myPack.getTrackId(), true); + seek_time(myPack.getTime(), myPack.getTrackId(), true); return seekNext(); } long long unsigned int version = 0; @@ -864,9 +895,12 @@ void DTSC::File::seekNext() { } currentPositions.insert(tmpPos); } else { - seek_time(myPack.getTime() + 1, myPack.getTrackId(), true); + seek_time(myPack.getTime(), myPack.getTrackId(), true); } seek_bpos(tempLoc); + }else{ + seek_time(thisPos.seekTime, thisPos.trackID); + fseek(F, thisPos.bytePos, SEEK_SET); } } @@ -954,9 +988,14 @@ DTSC::Packet & DTSC::File::getPacket() { 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()) { + if (!forceSeek && myPack && ms >= myPack.getTime() && trackNo >= myPack.getTrackId()) { tmpPos.seekTime = myPack.getTime(); tmpPos.bytePos = getBytePos(); + /* + if (trackNo == myPack.getTrackId()){ + tmpPos.bytePos += myPack.getDataLen(); + } + */ } else { tmpPos.seekTime = 0; tmpPos.bytePos = 0; diff --git a/lib/dtsc.h b/lib/dtsc.h index 4326c588..d83c335a 100644 --- a/lib/dtsc.h +++ b/lib/dtsc.h @@ -179,6 +179,26 @@ namespace DTSC { volatile int playCount; }; + /*LTS-START*/ + ///\brief Basic class supporting initialization Vectors. + /// + ///These are used for encryption of data. + class Ivec { + public: + Ivec(); + Ivec(long long int iVec); + void setIvec(long long int iVec); + void setIvec(std::string iVec); + void setIvec(char * iVec, int len); + long long int asInt(); + char * getData(); + private: + ///\brief Data storage for this initialization vector. + /// + /// - 8 bytes: MSB storage of the initialization vector. + char data[8]; + }; + /*LTS-END*/ ///\brief Basic class for storage of data associated with single DTSC packets, a.k.a. parts. class Part { @@ -270,6 +290,7 @@ namespace DTSC { std::deque keys; std::deque keySizes; std::deque parts; + std::deque ivecs; /*LTS*/ Key & getKey(unsigned int keyNum); unsigned int timeToKeynum(unsigned int timestamp); unsigned int timeToFragnum(unsigned int timestamp); @@ -394,6 +415,7 @@ namespace DTSC { std::string & outPacket(livePos num); std::string & outHeader(); Ring * getRing(); + void setRecord(std::string path); /*LTS*/ unsigned int getTime(); void dropRing(Ring * ptr); int canSeekms(unsigned int ms); @@ -411,9 +433,13 @@ namespace DTSC { std::map > keyframes; virtual void addPacket(JSON::Value & newPack); virtual void addMeta(JSON::Value & newMeta); + void recordPacket(JSON::Value & packet); /*LTS*/ datatype datapointertype; unsigned int buffercount; unsigned int buffertime; + std::string recordPath; /*LTS*/ + File * recordFile; /*LTS*/ + bool headerRecorded; /*LTS*/ std::map trackMapping; virtual void deletionCallback(livePos deleting); }; diff --git a/lib/dtscmeta.cpp b/lib/dtscmeta.cpp index e1f62386..6e482e75 100644 --- a/lib/dtscmeta.cpp +++ b/lib/dtscmeta.cpp @@ -790,7 +790,37 @@ namespace DTSC { } } + /*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(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 long Part::getSize() { @@ -978,6 +1008,12 @@ namespace DTSC { 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(); @@ -1024,6 +1060,14 @@ namespace DTSC { 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; + unsigned int 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(); @@ -1086,6 +1130,13 @@ namespace DTSC { } else { newKey.setBpos(0); } + /*LTS-START + if (pack.isMember("ivec")) { + Ivec newIvec; + newIvec.setIvec((char *)pack["ivec"].asString().data(), 8); + ivecs.push_back(newIvec); + } + LTS-END*/ keys.push_back(newKey); keySizes.push_back(0); firstms = keys[0].getTime(); @@ -1376,6 +1427,7 @@ namespace DTSC { result += 11 + (keySizes.size() * 4) + 4; } result += parts.size() * 9; + result += (ivecs.size() * 8) + 12; /*LTS*/ if (type == "audio") { result += 49; } else if (type == "video") { @@ -1433,6 +1485,13 @@ namespace DTSC { 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) { @@ -1503,6 +1562,13 @@ namespace DTSC { 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 (missedFrags) { @@ -1643,6 +1709,14 @@ namespace DTSC { 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["trackid"] = trackID; result["firstms"] = (long long)firstms; result["lastms"] = (long long)lastms; diff --git a/lib/encryption.cpp b/lib/encryption.cpp new file mode 100644 index 00000000..cb33ec89 --- /dev/null +++ b/lib/encryption.cpp @@ -0,0 +1,62 @@ +#include "encryption.h" +#include "auth.h" +//#include +#include +#include +#include +#include +#include + +namespace Encryption { + std::string AES_Crypt(const std::string & data, const std::string & key, std::string & ivec) { + unsigned char * outData = (unsigned char *)malloc(data.size() * sizeof(char)); + //unsigned int stateNum = 0; + unsigned char stateEcount[16]; + unsigned char stateIvec[16]; + memset(stateEcount, 0, 16); + memcpy(stateIvec, ivec.c_str(), 8); + memset(stateIvec + 8, 0, 8); + /// \todo Implement this ^_^ + //AES_KEY cryptKey; + //if (AES_set_encrypt_key((unsigned char *)key.c_str(), 128, &cryptKey)) { + // abort(); + //} + /// \todo loop per 128 bytes.... + //AES_ctr128_encrypt((unsigned char *)data.c_str(), outData, data.size(), &cryptKey, stateIvec, stateEcount, &stateNum); + std::string result = std::string((char *)outData, data.size()); + free(outData); + return result; + } + + 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.c_str(), 30); + memcpy(dataBlob+30, keyidBytes.data(), 16); + memcpy(dataBlob+46, keyseed.c_str(), 30); + memcpy(dataBlob+76, keyidBytes.data(), 16); + Secure::sha256bin(dataBlob, 46, keyA); + Secure::sha256bin(dataBlob, 76, keyB); + 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); + } + + std::string PR_GuidToByteArray(std::string & guid) { + std::string result; + result = guid[3]; + result += guid[2]; + result += guid[1]; + result += guid[0]; + result += guid[5]; + result += guid[4]; + result += guid[7]; + result += guid[6]; + result += guid.substr(8); + return result; + } +} diff --git a/lib/encryption.h b/lib/encryption.h new file mode 100644 index 00000000..5724b480 --- /dev/null +++ b/lib/encryption.h @@ -0,0 +1,8 @@ +#include + +namespace Encryption { + std::string AES_Crypt(const std::string & data, const std::string & key, std::string & ivec); + + std::string PR_GenerateContentKey(std::string & keyseed, std::string & keyid); + std::string PR_GuidToByteArray(std::string & guid); +} diff --git a/lib/mp4.cpp b/lib/mp4.cpp index 2ff3f469..aaff926c 100644 --- a/lib/mp4.cpp +++ b/lib/mp4.cpp @@ -4,7 +4,9 @@ #include "mp4.h" #include "mp4_adobe.h" #include "mp4_ms.h" +#include "mp4_dash.h" #include "mp4_generic.h" +#include "mp4_encryption.h" // /*LTS*/ #include "json.h" #include "defines.h" @@ -104,6 +106,7 @@ namespace MP4 { } } else if (size == 0) { fseek(newData, 0, SEEK_END); + return true; } DONTEVEN_MSG("skipping size 0x%.8lX", size); if (fseek(newData, pos + size, SEEK_SET) == 0) { @@ -132,6 +135,10 @@ namespace MP4 { return false; } } + if (size == 0){//no else if, because the extended size MAY be 0... + fseek(newData, 0, SEEK_END); + size = ftell(newData) - pos; + } fseek(newData, pos, SEEK_SET); data = (char *)realloc(data, size); data_size = size; @@ -160,6 +167,9 @@ namespace MP4 { return false; } } + if (size == 0){ + size = newData.size(); + } if (newData.size() >= size) { data = (char *)realloc(data, size); data_size = size; @@ -243,6 +253,9 @@ namespace MP4 { case 0x74666864: return ((TFHD *)this)->toPrettyString(indent); break; + case 0x68766343: + return ((HVCC *)this)->toPrettyString(indent); + break; case 0x61766343: return ((AVCC *)this)->toPrettyString(indent); break; @@ -252,6 +265,9 @@ namespace MP4 { case 0x66747970: return ((FTYP *)this)->toPrettyString(indent); break; + case 0x73747970: + return ((STYP*)this)->toPrettyString(indent); + break; case 0x6D6F6F76: return ((MOOV *)this)->toPrettyString(indent); break; @@ -344,11 +360,18 @@ namespace MP4 { break; case 0x6D703461://mp4a case 0x656E6361://enca + case 0x61632D33://ac-3 return ((MP4A *)this)->toPrettyString(indent); break; + case 0x64616333: + return ((DAC3 *)this)->toPrettyString(indent); + break; case 0x61616320: return ((AAC *)this)->toPrettyString(indent); break; + case 0x68657631: + return ((HEV1 *)this)->toPrettyString(indent); + break; case 0x61766331: return ((AVC1 *)this)->toPrettyString(indent); break; @@ -356,6 +379,15 @@ namespace MP4 { case 0x656E6376://encv return ((H264 *)this)->toPrettyString(indent); break; + case 0x6669656C: + return ((FIEL *)this)->toPrettyString(indent); + break; + case 0x74726566: + return ((TREF *)this)->toPrettyString(indent); + break; + case 0x676D6864: + return ((GMHD *)this)->toPrettyString(indent); + break; case 0x65647473: return ((EDTS *)this)->toPrettyString(indent); break; @@ -377,12 +409,36 @@ namespace MP4 { case 0x75756964: return ((UUID *)this)->toPrettyString(indent); break; + case 0x73696478: + return ((SIDX*)this)->toPrettyString(indent); + break; + case 0x74666474: + return ((TFDT*)this)->toPrettyString(indent); + break; + case 0x696F6473: + return ((IODS*)this)->toPrettyString(indent); + break; + /*LTS-START*/ + case 0x73696E66: + return ((SINF *)this)->toPrettyString(indent); + break; + case 0x66726D61: + return ((FRMA *)this)->toPrettyString(indent); + break; + case 0x7363686D: + return ((SCHM *)this)->toPrettyString(indent); + break; + case 0x73636869: + return ((SCHI *)this)->toPrettyString(indent); + break; + /*LTS-END*/ default: break; } - std::string retval = std::string(indent, ' ') + "Unimplemented pretty-printing for box " + std::string(data + 4, 4) + "\n"; + std::stringstream retval; + retval << std::string(indent, ' ') << "Unimplemented pretty-printing for box " << std::string(data + 4, 4) << " (" << ntohl(((int*)data)[0]) << ")\n"; /// \todo Implement hexdump for unimplemented boxes? - return retval; + return retval.str(); } /// Sets the 8 bits integer at the given index. @@ -804,4 +860,5 @@ namespace MP4 { } return r.str(); } + } diff --git a/lib/mp4_dash.cpp b/lib/mp4_dash.cpp new file mode 100644 index 00000000..29df00db --- /dev/null +++ b/lib/mp4_dash.cpp @@ -0,0 +1,240 @@ +#include "mp4_dash.h" +#include "defines.h" + +namespace MP4 { + SIDX::SIDX() { + memcpy(data + 4, "sidx", 4); + setVersion(0); + setFlags(0); + } + + void SIDX::setReferenceID(uint32_t newReferenceID) { + setInt32(newReferenceID, 4); + } + + uint32_t SIDX::getReferenceID() { + return getInt32(4); + } + + void SIDX::setTimescale(uint32_t newTimescale) { + setInt32(newTimescale, 8); + } + + uint32_t SIDX::getTimescale() { + return getInt32(8); + } + + void SIDX::setEarliestPresentationTime(uint64_t newEarliestPresentationTime) { + if (getVersion() == 0) { + setInt32(newEarliestPresentationTime, 12); + } else { + setInt64(newEarliestPresentationTime, 12); + } + } + + uint64_t SIDX::getEarliestPresentationTime() { + if (getVersion() == 0) { + return getInt32(12); + } + return getInt64(12); + } + + void SIDX::setFirstOffset(uint64_t newFirstOffset) { + if (getVersion() == 0) { + setInt32(newFirstOffset, 16); + } else { + setInt64(newFirstOffset, 20); + } + } + + uint64_t SIDX::getFirstOffset() { + if (getVersion() == 0) { + return getInt32(16); + } + return getInt64(20); + } + + uint16_t SIDX::getReferenceCount() { + if (getVersion() == 0) { + return getInt16(22); + } + return getInt16(30); + } + + void SIDX::setReference(sidxReference & newRef, size_t index) { + if (index >= getReferenceCount()) { + setInt16(index + 1, (getVersion() == 0 ? 22 : 30)); + } + uint32_t offset = 24 + (index * 12) + (getVersion() == 0 ? 0 : 8); + uint32_t tmp = (newRef.referenceType ? 0x80000000 : 0) | newRef.referencedSize; + setInt32(tmp, offset); + setInt32(newRef.subSegmentDuration, offset + 4); + tmp = (newRef.sapStart ? 0x80000000 : 0) | ((newRef.sapType & 0x7) << 24) | newRef.sapDeltaTime; + setInt32(tmp, offset + 8); + } + + sidxReference SIDX::getReference(size_t index) { + sidxReference result; + if (index >= getReferenceCount()) { + DEBUG_MSG(DLVL_DEVEL, "Warning, attempt to obtain reference out of bounds"); + return result; + } + uint32_t offset = 24 + (index * 12) + (getVersion() == 0 ? 0 : 8); + uint32_t tmp = getInt32(offset); + result.referenceType = tmp & 0x80000000; + result.referencedSize = tmp & 0x7FFFFFFF; + result.subSegmentDuration = getInt32(offset + 4); + tmp = getInt32(offset + 8); + result.sapStart = tmp & 0x80000000; + result.sapType = (tmp & 0x70000000) >> 24; + result.sapDeltaTime = (tmp & 0x0FFFFFFF); + return result; + } + + std::string SIDX::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[sidx] Segment Index Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "ReferenceID " << getReferenceID() << std::endl; + r << std::string(indent + 1, ' ') << "Timescale " << getTimescale() << std::endl; + r << std::string(indent + 1, ' ') << "EarliestPresentationTime " << getEarliestPresentationTime() << std::endl; + r << std::string(indent + 1, ' ') << "FirstOffset " << getFirstOffset() << std::endl; + r << std::string(indent + 1, ' ') << "References [" << getReferenceCount() << "]" << std::endl; + for (int i = 0; i < getReferenceCount(); i++) { + sidxReference tmp = getReference(i); + r << std::string(indent + 2, ' ') << "[" << i << "]" << std::endl; + r << std::string(indent + 3, ' ') << "ReferenceType " << (int)tmp.referenceType << std::endl; + r << std::string(indent + 3, ' ') << "ReferencedSize " << tmp.referencedSize << std::endl; + r << std::string(indent + 3, ' ') << "SubSegmentDuration " << tmp.subSegmentDuration << std::endl; + r << std::string(indent + 3, ' ') << "StartsWithSAP " << (int)tmp.sapStart << std::endl; + r << std::string(indent + 3, ' ') << "SAP Type " << (int)tmp.sapType << std::endl; + r << std::string(indent + 3, ' ') << "SAP DeltaTime " << tmp.sapDeltaTime << std::endl; + } + return r.str(); + } + + TFDT::TFDT() { + memcpy(data + 4, "tfdt", 4); + setVersion(0); + setFlags(0); + } + + void TFDT::setBaseMediaDecodeTime(uint64_t newBaseMediaDecodeTime) { + if (getVersion() == 1) { + setInt64(newBaseMediaDecodeTime, 4); + } else { + setInt32(newBaseMediaDecodeTime, 4); + } + } + + uint64_t TFDT::getBaseMediaDecodeTime() { + if (getVersion() == 1) { + return getInt64(4); + } + return getInt32(4); + } + + std::string TFDT::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[tfdt] Track Fragment Base Media Decode Time Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "BaseMediaDecodeTime " << getBaseMediaDecodeTime() << std::endl; + return r.str(); + } + + IODS::IODS() { + memcpy(data + 4, "iods", 4); + setVersion(0); + setFlags(0); + setIODTypeTag(0x10); + setDescriptorTypeLength(0x07); + setODID(0x004F); + setODProfileLevel(0xFF); + setODSceneLevel(0xFF); + setODAudioLevel(0xFF); + setODVideoLevel(0xFF); + setODGraphicsLevel(0xFF); + } + + void IODS::setIODTypeTag(char value) { + setInt8(value, 4); + } + + char IODS::getIODTypeTag() { + return getInt8(4); + } + + void IODS::setDescriptorTypeLength(char length) { + setInt8(length, 5); + } + + char IODS::getDescriptorTypeLength() { + return getInt8(5); + } + + void IODS::setODID(short id) { + setInt16(id, 6); + } + + short IODS::getODID() { + return getInt16(6); + } + + void IODS::setODProfileLevel(char value) { + setInt8(value, 8); + } + + char IODS::getODProfileLevel() { + return getInt8(8); + } + + void IODS::setODSceneLevel(char value) { + setInt8(value, 9); + } + + char IODS::getODSceneLevel() { + return getInt8(9); + } + + void IODS::setODAudioLevel(char value) { + setInt8(value, 10); + } + + char IODS::getODAudioLevel() { + return getInt8(10); + } + + void IODS::setODVideoLevel(char value) { + setInt8(value, 11); + } + + char IODS::getODVideoLevel() { + return getInt8(11); + } + + void IODS::setODGraphicsLevel(char value) { + setInt8(value, 12); + } + + char IODS::getODGraphicsLevel() { + return getInt8(12); + } + + + std::string IODS::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[iods] IODS Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 2, ' ') << "IOD Type Tag: " << std::hex << std::setw(2) << std::setfill('0') << (int)getIODTypeTag() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "DescriptorTypeLength: " << std::hex << std::setw(2) << std::setfill('0') << (int)getDescriptorTypeLength() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "OD ID: " << std::hex << std::setw(4) << std::setfill('0') << (int)getODID() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "OD Profile Level: " << std::hex << std::setw(2) << std::setfill('0') << (int)getODProfileLevel() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "OD Scene Level: " << std::hex << std::setw(2) << std::setfill('0') << (int)getODSceneLevel() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "OD Audio Level: " << std::hex << std::setw(2) << std::setfill('0') << (int)getODAudioLevel() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "OD Video Level: " << std::hex << std::setw(2) << std::setfill('0') << (int)getODVideoLevel() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "OD Graphics Level: " << std::hex << std::setw(2) << std::setfill('0') << (int)getODGraphicsLevel() << std::dec << std::endl; + return r.str(); + } +} + + diff --git a/lib/mp4_dash.h b/lib/mp4_dash.h new file mode 100644 index 00000000..4b76f9d7 --- /dev/null +++ b/lib/mp4_dash.h @@ -0,0 +1,73 @@ +#pragma once +#include "mp4.h" + +namespace MP4 { + struct sidxReference { + bool referenceType; + uint32_t referencedSize; + uint32_t subSegmentDuration; + bool sapStart; + uint8_t sapType; + uint32_t sapDeltaTime; + }; + + class SIDX: public fullBox { + public: + SIDX(); + void setReferenceID(uint32_t newReferenceID); + uint32_t getReferenceID(); + void setTimescale(uint32_t newTimescale); + uint32_t getTimescale(); + + void setEarliestPresentationTime(uint64_t newEarliestPresentationTime); + uint64_t getEarliestPresentationTime(); + void setFirstOffset(uint64_t newFirstOffset); + uint64_t getFirstOffset(); + + uint16_t getReferenceCount(); + void setReference(sidxReference & newRef, size_t index); + sidxReference getReference(size_t index); + + std::string toPrettyString(uint32_t indent = 0); + }; + + class TFDT: public fullBox { + public: + TFDT(); + void setBaseMediaDecodeTime(uint64_t newBaseMediaDecodeTime); + uint64_t getBaseMediaDecodeTime(); + + std::string toPrettyString(uint32_t indent = 0); + }; + + class IODS: public fullBox { + public: + IODS(); + void setIODTypeTag(char value); + char getIODTypeTag(); + + void setDescriptorTypeLength(char length); + char getDescriptorTypeLength(); + + void setODID(short id); + short getODID(); + + void setODProfileLevel(char value); + char getODProfileLevel(); + + void setODSceneLevel(char value); + char getODSceneLevel(); + + void setODAudioLevel(char value); + char getODAudioLevel(); + + void setODVideoLevel(char value); + char getODVideoLevel(); + + void setODGraphicsLevel(char value); + char getODGraphicsLevel(); + + std::string toPrettyString(uint32_t indent = 0); + }; +} + diff --git a/lib/mp4_encryption.cpp b/lib/mp4_encryption.cpp new file mode 100644 index 00000000..00ca082e --- /dev/null +++ b/lib/mp4_encryption.cpp @@ -0,0 +1,456 @@ +#include "mp4_encryption.h" + +namespace MP4 { + + UUID_SampleEncryption::UUID_SampleEncryption() { + setUUID((std::string)"a2394f52-5a9b-4f14-a244-6c427c648df4"); + } + + void UUID_SampleEncryption::setVersion(uint32_t newVersion) { + setInt8(newVersion, 16); + } + + uint32_t UUID_SampleEncryption::getVersion() { + return getInt8(16); + } + + void UUID_SampleEncryption::setFlags(uint32_t newFlags) { + setInt24(newFlags, 17); + } + + uint32_t UUID_SampleEncryption::getFlags() { + return getInt24(17); + } + + void UUID_SampleEncryption::setAlgorithmID(uint32_t newAlgorithmID) { + if (getFlags() & 0x01) { + setInt24(newAlgorithmID, 20); + } + } + + uint32_t UUID_SampleEncryption::getAlgorithmID() { + if (getFlags() & 0x01) { + return getInt24(20); + } + return -1; + } + + void UUID_SampleEncryption::setIVSize(uint32_t newIVSize) { + if (getFlags() & 0x01) { + setInt8(newIVSize, 23); + } + } + + uint32_t UUID_SampleEncryption::getIVSize() { + if (getFlags() & 0x01) { + return getInt8(23); + } + return -1; + } + + void UUID_SampleEncryption::setKID(std::string newKID) { + if (newKID == "") { + return; + } + if (getFlags() & 0x01) { + while (newKID.size() < 16) { + newKID += (char)0x00; + } + for (int i = 0; i < 16; i++) { + setInt8(newKID[i], 24 + i); + } + } + } + + std::string UUID_SampleEncryption::getKID() { + if (getFlags() & 0x01) { + std::string result; + for (int i = 0; i < 16; i++) { + result += (char)getInt8(24 + i); + } + return result; + } + return ""; + } + + uint32_t UUID_SampleEncryption::getSampleCount() { + int myOffset = 20; + if (getFlags() & 0x01) { + myOffset += 20; + } + return getInt32(myOffset); + } + +#define IV_SIZE 8 + void UUID_SampleEncryption::setSample(UUID_SampleEncryption_Sample newSample, size_t index) { + int myOffset = 20; + myOffset += 20 * (getFlags() & 0x01); + myOffset += 4;//sampleCount is here; + for (unsigned int i = 0; i < std::min(index, (size_t)getSampleCount()); i++) { + myOffset += IV_SIZE; + if (getFlags() & 0x02) { + int entryCount = getInt16(myOffset); + myOffset += 2 + (entryCount * 6); + } + } + if (index >= getSampleCount()) { + //we are now at the end of currently reserved space, reserve more and adapt offset accordingly. + int reserveSize = ((index - getSampleCount())) * (IV_SIZE + (getFlags() & 0x02)); + reserveSize += IV_SIZE; + if (getFlags() & 0x02) { + reserveSize += 2 + newSample.Entries.size(); + } + if (!reserve(myOffset, 0, reserveSize)) { + return;//Memory errors... + } + myOffset += (index - getSampleCount()) * (IV_SIZE + (getFlags() & 0x02)); + } + //write box. + for (int i = 0; i < IV_SIZE; i++) { + setInt8(newSample.InitializationVector[i], myOffset ++);//set and skip + } + if (getFlags() & 0x02) { + setInt16(newSample.Entries.size(), myOffset); + myOffset += 2; + for (std::vector::iterator it = newSample.Entries.begin(); it != newSample.Entries.end(); it++) { + setInt16(it->BytesClear, myOffset); + myOffset += 2; + setInt32(it->BytesEncrypted, myOffset); + myOffset += 4; + } + } + if (index >= getSampleCount()) { + setInt32(index + 1, 20 + (20 * (getFlags() & 0x01))); + } + } + + UUID_SampleEncryption_Sample UUID_SampleEncryption::getSample(size_t index) { + if (index >= getSampleCount()) { + return UUID_SampleEncryption_Sample(); + } + int myOffset = 20; + myOffset += 20 * (getFlags() & 0x01); + myOffset += 4;//sampleCount is here + for (unsigned int i = 0; i < index; i++) { + myOffset += IV_SIZE; + if (getFlags() & 0x02) { + int entryCount = getInt16(myOffset); + myOffset += 2;//skip over entrycount + myOffset += entryCount * 6;//skip entryCount sample entries + } + } + UUID_SampleEncryption_Sample result; + for (int i = 0; i < IV_SIZE; i++) { + result.InitializationVector += getInt8(myOffset++);//read and skip + } + if (getFlags() & 0x02) { + result.NumberOfEntries = getInt16(myOffset); + myOffset += 2; + for (unsigned int i = 0; i < result.NumberOfEntries; i++) { + result.Entries.push_back(UUID_SampleEncryption_Sample_Entry()); + result.Entries[i].BytesClear = getInt16(myOffset); + myOffset += 2; + result.Entries[i].BytesEncrypted = getInt32(myOffset); + myOffset += 4; + } + } + return result; + } + + std::string UUID_SampleEncryption::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[a2394f52-5a9b-4f14-a244-6c427c648df4] Sample Encryption Box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version: " << getVersion() << std::endl; + r << std::string(indent + 1, ' ') << "Flags: " << getFlags() << std::endl; + if (getFlags() & 0x01) { + r << std::string(indent + 1, ' ') << "Algorithm ID: " << getAlgorithmID() << std::endl; + r << std::string(indent + 1, ' ') << "IV Size: " << getIVSize() << std::endl; + r << std::string(indent + 1, ' ') << "Key ID: " << getKID() << std::endl; + } + r << std::string(indent + 1, ' ') << "Sample Count: " << getSampleCount() << std::endl; + for (unsigned int i = 0; i < getSampleCount(); i++) { + UUID_SampleEncryption_Sample tmpSample = getSample(i); + r << std::string(indent + 1, ' ') << "[" << i << "]" << std::endl; + r << std::string(indent + 3, ' ') << "Initialization Vector: 0x"; + for (unsigned int j = 0; j < tmpSample.InitializationVector.size(); j++) { + r << std::hex << std::setw(2) << std::setfill('0') << (int)tmpSample.InitializationVector[j] << std::dec; + } + r << std::endl; + if (getFlags() & 0x02) { + r << std::string(indent + 3, ' ') << "Number of entries: " << tmpSample.NumberOfEntries << std::endl; + for (unsigned int j = 0; j < tmpSample.NumberOfEntries; j++) { + r << std::string(indent + 3, ' ') << "[" << j << "]" << std::endl; + r << std::string(indent + 5, ' ') << "Bytes clear: " << tmpSample.Entries[j].BytesClear << std::endl; + r << std::string(indent + 5, ' ') << "Bytes encrypted: " << tmpSample.Entries[j].BytesEncrypted << std::endl; + } + } + } + return r.str(); + } + + UUID_TrackEncryption::UUID_TrackEncryption() { + setUUID((std::string)"8974dbce-7be7-4c51-84f9-7148f9882554"); + } + + void UUID_TrackEncryption::setVersion(uint32_t newVersion) { + setInt8(newVersion, 16); + } + + uint32_t UUID_TrackEncryption::getVersion() { + return getInt8(16); + } + + void UUID_TrackEncryption::setFlags(uint32_t newFlags) { + setInt24(newFlags, 17); + } + + uint32_t UUID_TrackEncryption::getFlags() { + return getInt24(17); + } + + void UUID_TrackEncryption::setDefaultAlgorithmID(uint32_t newID) { + setInt24(newID, 20); + } + + uint32_t UUID_TrackEncryption::getDefaultAlgorithmID() { + return getInt24(20); + } + + void UUID_TrackEncryption::setDefaultIVSize(uint8_t newIVSize) { + setInt8(newIVSize, 23); + } + + uint8_t UUID_TrackEncryption::getDefaultIVSize() { + return getInt8(23); + } + + void UUID_TrackEncryption::setDefaultKID(std::string newKID) { + for (unsigned int i = 0; i < 16; i++) { + if (i < newKID.size()) { + setInt8(newKID[i], 24 + i); + } else { + setInt8(0x00, 24 + i); + } + } + } + + std::string UUID_TrackEncryption::getDefaultKID() { + std::string result; + for (int i = 0; i < 16; i++) { + result += getInt8(24 + i); + } + return result; + } + + std::string UUID_TrackEncryption::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[8974dbce-7be7-4c51-84f9-7148f9882554] Track Encryption Box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 2, ' ') << "Version: " << getVersion() << std::endl; + r << std::string(indent + 2, ' ') << "Flags: " << getFlags() << std::endl; + r << std::string(indent + 2, ' ') << "Default Algorithm ID: " << std::hex << getDefaultAlgorithmID() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "Default IV Size: " << (int)getDefaultIVSize() << std::endl; + r << std::string(indent + 2, ' ') << "Default KID: 16 bytes of binary data." << std::endl; + return r.str(); + } + + UUID_ProtectionSystemSpecificHeader::UUID_ProtectionSystemSpecificHeader() { + setUUID((std::string)"d08a4f18-10f3-4a82-b6c8-32d8aba183d3"); + } + + void UUID_ProtectionSystemSpecificHeader::setVersion(uint32_t newVersion) { + setInt8(newVersion, 16); + } + + uint32_t UUID_ProtectionSystemSpecificHeader::getVersion() { + return getInt8(16); + } + + void UUID_ProtectionSystemSpecificHeader::setFlags(uint32_t newFlags) { + setInt24(newFlags, 17); + } + + uint32_t UUID_ProtectionSystemSpecificHeader::getFlags() { + return getInt24(17); + } + + void UUID_ProtectionSystemSpecificHeader::setSystemID(std::string newID) { + for (unsigned int i = 0; i < 16; i++) { + if (i < newID.size()) { + setInt8(newID[i], 20 + i); + } else { + setInt8(0x00, 20 + i); + } + } + } + + std::string UUID_ProtectionSystemSpecificHeader::getSystemID() { + std::string result; + for (int i = 0; i < 16; i++) { + result += getInt8(20 + i); + } + return result; + } + + uint32_t UUID_ProtectionSystemSpecificHeader::getDataSize() { + return getInt32(36); + } + + void UUID_ProtectionSystemSpecificHeader::setData(std::string newData) { + setInt32(newData.size(), 36); + for (unsigned int i = 0; i < newData.size(); i++) { + setInt8(newData[i], 40 + i); + } + } + + std::string UUID_ProtectionSystemSpecificHeader::getData() { + std::string result; + for (unsigned int i = 0; i < getDataSize(); i++) { + result += getInt8(40 + i); + } + return result; + } + + std::string UUID_ProtectionSystemSpecificHeader::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[d08a4f18-10f3-4a82-b6c8-32d8aba183d3] Protection System Specific Header Box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 2, ' ') << "Version: " << getVersion() << std::endl; + r << std::string(indent + 2, ' ') << "Flags: " << getFlags() << std::endl; + r << std::string(indent + 2, ' ') << "System ID: " << getSystemID() << std::endl; + r << std::string(indent + 2, ' ') << "Data Size: " << getDataSize() << std::endl; + r << std::string(indent + 2, ' ') << "Data: " << getData().size() << " bytes of data." << std::endl; + return r.str(); + } + + SINF::SINF() { + memcpy(data + 4, "sinf", 4); + } + + void SINF::setEntry(Box & newEntry, uint32_t no) { + if (no > 4) { + return; + } + int tempLoc = 0; + for (unsigned int i = 0; i < no; i++) { + tempLoc += Box(getBox(tempLoc).asBox(), false).boxedSize(); + } + setBox(newEntry, tempLoc); + } + + Box & SINF::getEntry(uint32_t no) { + static Box ret = Box((char *)"\000\000\000\010erro", false); + if (no > 4) { + ret = Box((char *)"\000\000\000\010erro", false); + return ret; + } + int tempLoc = 0; + for (unsigned int i = 0; i < no; i++) { + tempLoc += Box(getBox(tempLoc).asBox(), false).boxedSize(); + } + ret = Box(getBox(tempLoc).asBox(), false); + return ret; + } + + std::string SINF::toPrettyString(uint32_t indent) { + std::stringstream r; + std::cerr << payloadOffset << std::endl; + r << std::string(indent, ' ') << "[sinf] Protection Scheme Info Box (" << boxedSize() << ")" << std::endl; + for (int i = 0; i < 4; i++) { + if (!getEntry(i).isType("erro")) { + r << getEntry(i).toPrettyString(indent + 2); + } + } + r << std::endl; + return r.str(); + } + + FRMA::FRMA() { + memcpy(data + 4, "frma", 4); + } + + void FRMA::setOriginalFormat(std::string newFormat) { + for (unsigned int i = 0; i < 4; i++) { + if (i < newFormat.size()) { + setInt8(newFormat[i], i); + } else { + setInt8(0x00, i); + } + } + } + + std::string FRMA::getOriginalFormat() { + return std::string(payload(), 4); + } + + std::string FRMA::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[frma] Original Format Box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 2, ' ') << "Original Format: " << getOriginalFormat() << std::endl; + return r.str(); + } + + SCHM::SCHM() { + memcpy(data + 4, "schm", 4); + } + + void SCHM::setSchemeType(uint32_t newType) { + setInt32(htonl(newType), 4); + } + + uint32_t SCHM::getSchemeType() { + return ntohl(getInt32(4)); + } + + void SCHM::setSchemeVersion(uint32_t newVersion) { + setInt32(htonl(newVersion), 8); + } + + uint32_t SCHM::getSchemeVersion() { + return ntohl(getInt32(8)); + } + + void SCHM::setSchemeURI(std::string newURI) { + setFlags(getFlags() | 0x01); + setString(newURI, 12); + } + + std::string SCHM::getSchemeURI() { + if (getFlags() & 0x01) { + return getString(12); + } + return ""; + } + + std::string SCHM::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[schm] Scheme Type Box (" << boxedSize() << ")" << std::endl; + char tmpStr[10]; + int tmpInt = getSchemeType(); + sprintf(tmpStr, "%.4s", (char *)&tmpInt); + r << std::string(indent + 2, ' ') << "SchemeType: " << std::string(tmpStr, 4) << std::endl; + r << std::string(indent + 2, ' ') << "SchemeVersion: 0x" << std::hex << std::setw(8) << std::setfill('0') << getSchemeVersion() << std::dec << std::endl; + if (getFlags() & 0x01) { + r << std::string(indent + 2, ' ') << "SchemeURI: " << getSchemeURI() << std::endl; + } + return r.str(); + } + + SCHI::SCHI() { + memcpy(data + 4, "schi", 4); + } + + void SCHI::setContent(Box & newContent) { + setBox(newContent, 0); + } + + Box & SCHI::getContent() { + return getBox(0); + } + + std::string SCHI::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[schi] Scheme Information Box (" << boxedSize() << ")" << std::endl; + r << getContent().toPrettyString(indent + 2); + return r.str(); + } + +} diff --git a/lib/mp4_encryption.h b/lib/mp4_encryption.h new file mode 100644 index 00000000..23db32d1 --- /dev/null +++ b/lib/mp4_encryption.h @@ -0,0 +1,104 @@ +#include "mp4.h" +#include "mp4_ms.h" + +namespace MP4 { + + struct UUID_SampleEncryption_Sample_Entry { + uint32_t BytesClear; + uint32_t BytesEncrypted; + }; + + struct UUID_SampleEncryption_Sample { + std::string InitializationVector; + uint32_t NumberOfEntries; + std::vector Entries; + }; + + class UUID_SampleEncryption: public UUID { + public: + UUID_SampleEncryption(); + void setVersion(uint32_t newVersion); + uint32_t getVersion(); + void setFlags(uint32_t newFlags); + uint32_t getFlags(); + void setAlgorithmID(uint32_t newAlgorithmID); + uint32_t getAlgorithmID(); + void setIVSize(uint32_t newIVSize); + uint32_t getIVSize(); + void setKID(std::string newKID); + std::string getKID(); + uint32_t getSampleCount(); + void setSample(UUID_SampleEncryption_Sample newSample, size_t index); + UUID_SampleEncryption_Sample getSample(size_t index); + std::string toPrettyString(uint32_t indent = 0); + }; + + class UUID_TrackEncryption: public UUID { + public: + UUID_TrackEncryption(); + void setVersion(uint32_t newVersion); + uint32_t getVersion(); + void setFlags(uint32_t newFlags); + uint32_t getFlags(); + void setDefaultAlgorithmID(uint32_t newAlgorithmID); + uint32_t getDefaultAlgorithmID(); + void setDefaultIVSize(uint8_t newIVSize); + uint8_t getDefaultIVSize(); + void setDefaultKID(std::string newKID); + std::string getDefaultKID(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class UUID_ProtectionSystemSpecificHeader: public UUID { + public: + UUID_ProtectionSystemSpecificHeader(); + void setVersion(uint32_t newVersion); + uint32_t getVersion(); + void setFlags(uint32_t newFlags); + uint32_t getFlags(); + void setSystemID(std::string newID); + std::string getSystemID(); + void setDataSize(uint32_t newDataSize); + uint32_t getDataSize(); + void setData(std::string newData); + std::string getData(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class SINF: public Box { + public: + SINF(); + void setEntry(Box & newEntry, uint32_t no); + Box & getEntry(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + + class FRMA: public Box { + public: + FRMA(); + void setOriginalFormat(std::string newFormat); + std::string getOriginalFormat(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class SCHM: public fullBox { + public: + SCHM(); + void setSchemeType(uint32_t newType); + uint32_t getSchemeType(); + void setSchemeVersion(uint32_t newVersion); + uint32_t getSchemeVersion(); + void setSchemeURI(std::string newURI); + std::string getSchemeURI(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class SCHI: public Box { + public: + SCHI(); + void setContent(Box & newContent); + Box & getContent(); + std::string toPrettyString(uint32_t indent = 0); + }; + +} diff --git a/lib/mp4_generic.cpp b/lib/mp4_generic.cpp index bf9d4bc3..6fbd3875 100644 --- a/lib/mp4_generic.cpp +++ b/lib/mp4_generic.cpp @@ -639,6 +639,192 @@ namespace MP4 { memcpy((char *)payload(), (char *)newPayload.c_str(), newPayload.size()); } + HVCC::HVCC() { + memcpy(data + 4, "hvcC", 4); + } + + void HVCC::setConfigurationVersion(char newVersion) { + setInt8(newVersion, 0); + } + char HVCC::getConfigurationVersion() { + return getInt8(0); + } + + void HVCC::setGeneralProfileSpace(char newGeneral) { + setInt8(((newGeneral << 6) & 0xC0) | (getInt8(1) & 0x3F), 1); + } + char HVCC::getGeneralProfileSpace(){ + return ((getInt8(1) >> 6) & 0x03); + } + + void HVCC::setGeneralTierFlag(char newGeneral){ + setInt8(((newGeneral << 5) & 0x20) | (getInt8(1) & 0xDF), 1); + } + char HVCC::getGeneralTierFlag(){ + return ((getInt8(1) >> 5) & 0x01); + } + + void HVCC::setGeneralProfileIdc(char newGeneral){ + setInt8((newGeneral & 0x1F) | (getInt8(1) & 0xE0), 1); + } + char HVCC::getGeneralProfileIdc(){ + return getInt8(1) & 0x1F; + } + + void HVCC::setGeneralProfileCompatibilityFlags(unsigned long newGeneral){ + setInt32(newGeneral, 2); + } + unsigned long HVCC::getGeneralProfileCompatibilityFlags(){ + return getInt32(2); + } + + void HVCC::setGeneralConstraintIndicatorFlags(unsigned long long newGeneral){ + setInt32((newGeneral >> 16) & 0x0000FFFF,6); + setInt16(newGeneral & 0xFFFFFF, 10); + } + unsigned long long HVCC::getGeneralConstraintIndicatorFlags(){ + unsigned long long result = getInt32(6); + result <<= 16; + return result | getInt16(10); + } + + void HVCC::setGeneralLevelIdc(char newGeneral){ + setInt8(newGeneral, 12); + } + char HVCC::getGeneralLevelIdc(){ + return getInt8(12); + } + + void HVCC::setMinSpatialSegmentationIdc(short newIdc){ + setInt16(newIdc | 0xF000, 13); + } + short HVCC::getMinSpatialSegmentationIdc(){ + return getInt16(13) & 0x0FFF; + } + + void HVCC::setParallelismType(char newType){ + setInt8(newType | 0xFC, 15); + } + char HVCC::getParallelismType(){ + return getInt8(15) & 0x03; + } + + void HVCC::setChromaFormat(char newFormat){ + setInt8(newFormat | 0xFC, 16); + } + char HVCC::getChromaFormat(){ + return getInt8(16) & 0x03; + } + + void HVCC::setBitDepthLumaMinus8(char newBitDepthLumaMinus8){ + setInt8(newBitDepthLumaMinus8 | 0xF8, 17); + } + char HVCC::getBitDepthLumaMinus8(){ + return getInt8(17) & 0x07; + } + + void HVCC::setBitDepthChromaMinus8(char newBitDepthChromaMinus8){ + setInt8(newBitDepthChromaMinus8 | 0xF8, 18); + } + char HVCC::getBitDepthChromaMinus8(){ + return getInt8(18) & 0x07; + } + + void setAverageFramerate(short newFramerate); + short HVCC::getAverageFramerate(){ + return getInt16(19); + } + void setConstantFramerate(char newFramerate); + char HVCC::getConstantFramerate(){ + return (getInt8(21) >> 6) & 0x03; + } + void setNumberOfTemporalLayers(char newNumber); + char HVCC::getNumberOfTemporalLayers(){ + return (getInt8(21) >> 3) & 0x07; + } + void setTemporalIdNested(char newNested); + char HVCC::getTemporalIdNested(){ + return (getInt8(21) >> 2) & 0x01; + } + void setLengthSizeMinus1(char newLengthSizeMinus1); + char HVCC::getLengthSizeMinus1(){ + return getInt8(21) & 0x03; + } + + std::deque HVCC::getArrays(){ + std::deque r; + char arrayCount = getInt8(22); + int offset = 23; + for(int i = 0; i < arrayCount; i++){ + HVCCArrayEntry entry; + entry.arrayCompleteness = ((getInt8(offset) >> 7) & 0x01); + entry.nalUnitType = (getInt8(offset) & 0x3F); + offset++; + short naluCount = getInt16(offset); + offset += 2; + for (int j = 0; j < naluCount; j++){ + short naluSize = getInt16(offset); + offset += 2; + std::string nalu; + for (int k = 0; k < naluSize; k++){ + nalu += (char)getInt8(offset++); + } + entry.nalUnits.push_back(nalu); + } + r.push_back(entry); + } + return r; + } + + std::string HVCC::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[hvcC] H.265 Init Data (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Configuration Version: " << (int)getConfigurationVersion() << std::endl; + r << std::string(indent + 1, ' ') << "General Profile Space: " << (int)getGeneralProfileSpace() << std::endl; + r << std::string(indent + 1, ' ') << "General Tier Flag: " << (int)getGeneralTierFlag() << std::endl; + r << std::string(indent + 1, ' ') << "General Profile IDC: " << (int)getGeneralProfileIdc() << std::endl; + r << std::string(indent + 1, ' ') << "General Profile Compatibility Flags: 0x" << std::hex << std::setw(8) << std::setfill('0') << getGeneralProfileCompatibilityFlags() << std::dec << std::endl; + r << std::string(indent + 1, ' ') << "General Constraint Indicator Flags: 0x" << std::hex << std::setw(12) << std::setfill('0') << getGeneralConstraintIndicatorFlags() << std::dec << std::endl; + r << std::string(indent + 1, ' ') << "General Level IDC: " << (int)getGeneralLevelIdc() << std::endl; + r << std::string(indent + 1, ' ') << "Minimum Spatial Segmentation IDC: " << (int)getMinSpatialSegmentationIdc() << std::endl; + r << std::string(indent + 1, ' ') << "Parallelism Type: " << (int)getParallelismType() << std::endl; + r << std::string(indent + 1, ' ') << "Chroma Format: " << (int)getChromaFormat() << std::endl; + r << std::string(indent + 1, ' ') << "Bit Depth Luma - 8: " << (int)getBitDepthLumaMinus8() << std::endl; + r << std::string(indent + 1, ' ') << "Average Framerate: " << (int)getAverageFramerate() << std::endl; + r << std::string(indent + 1, ' ') << "Constant Framerate: " << (int)getConstantFramerate() << std::endl; + r << std::string(indent + 1, ' ') << "Number of Temporal Layers: " << (int)getNumberOfTemporalLayers() << std::endl; + r << std::string(indent + 1, ' ') << "Temporal ID Nested: " << (int)getTemporalIdNested() << std::endl; + r << std::string(indent + 1, ' ') << "Length Size - 1: " << (int)getLengthSizeMinus1() << std::endl; + r << std::string(indent + 1, ' ') << "Arrays:" << std::endl; + std::deque arrays = getArrays(); + for (unsigned int i = 0; i < arrays.size(); i++){ + r << std::string(indent + 2, ' ') << "Array with type " << (int)arrays[i].nalUnitType << std::endl; + for (unsigned int j = 0; j < arrays[i].nalUnits.size(); j++){ + r << std::string(indent + 3, ' ') << "Nal unit of " << arrays[i].nalUnits[j].size() << " bytes" << std::endl; + } + } + return r.str(); + } + + void HVCC::setPayload(std::string newPayload) { + if (!reserve(0, payloadSize(), newPayload.size())) { + DEBUG_MSG(DLVL_ERROR, "Cannot allocate enough memory for payload"); + return; + } + memcpy((char *)payload(), (char *)newPayload.c_str(), newPayload.size()); + } + + std::string HVCC::asAnnexB() { + std::deque arrays = getArrays(); + std::stringstream r; + for (unsigned int i = 0; i < arrays.size(); i++){ + for (unsigned int j = 0; j < arrays[i].nalUnits.size(); j++){ + r << (char)0x00 << (char)0x00 << (char)0x00 << (char)0x01 << arrays[i].nalUnits[j]; + } + } + return r.str(); + } + Descriptor::Descriptor(){ data = (char*)malloc(2); data[0] = 0; @@ -967,19 +1153,85 @@ namespace MP4 { return r.str(); } - FTYP::FTYP() { - memcpy(data + 4, "ftyp", 4); - setMajorBrand("mp41"); - setMinorVersion("Mist"); - setCompatibleBrands("isom", 0); - setCompatibleBrands("iso2", 1); - setCompatibleBrands("avc1", 2); - setCompatibleBrands("mp41", 3); + DAC3::DAC3(){ + memcpy(data + 4, "dac3", 4); + setInt24(0,0); } - void FTYP::setMajorBrand(const char * newMajorBrand) { - if (payloadOffset + 3 >= boxedSize()) { - if (!reserve(payloadOffset, 0, 4)) { + char DAC3::getSampleRateCode(){ + return getInt8(0) >> 6; + } + + void DAC3::setSampleRateCode(char newVal){ + setInt8(((newVal << 6) & 0xC0) | (getInt8(0) & 0x3F), 0); + } + + char DAC3::getBitStreamIdentification(){ + return (getInt8(0) >> 1) & 0x1F; + } + + void DAC3::setBitStreamIdentification(char newVal){ + setInt8(((newVal << 1) & 0x3E) | (getInt8(0) & 0xC1), 0); + } + + char DAC3::getBitStreamMode(){ + return (getInt16(0) >> 6) & 0x7; + } + + void DAC3::setBitStreamMode(char newVal){ + setInt16(((newVal & 0x7) << 6) | (getInt16(0) & 0xFE3F), 0); + } + + char DAC3::getAudioConfigMode(){ + return (getInt8(1) >> 3) & 0x7; + } + + void DAC3::setAudioConfigMode(char newVal){ + setInt8(((newVal & 0x7) << 3) | (getInt8(1) & 0x38),1); + } + + bool DAC3::getLowFrequencyEffectsChannelOn(){ + return (getInt8(1) >> 2) & 0x1; + } + + void DAC3::setLowFrequencyEffectsChannelOn(bool newVal){ + setInt8(((unsigned int)(newVal & 0x1) << 2) | (getInt8(1) & 0x4),1); + } + + char DAC3::getFrameSizeCode(){ + return ((getInt8(1) & 0x3) << 4) | ((getInt8(2) & 0xF0) >> 4); + } + + void DAC3::setFrameSizeCode(char newVal){ + setInt16(((newVal & 0x3F) << 4) | (getInt16(1) & 0x03F0),1); + } + + std::string DAC3::toPrettyString(uint32_t indent){ + std::stringstream r; + r << std::string(indent, ' ') << "[dac3] D-AC3 box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "FSCOD: " << (int)getSampleRateCode() << std::endl; + r << std::string(indent + 1, ' ') << "BSID: " << (int)getBitStreamIdentification() << std::endl; + r << std::string(indent + 1, ' ') << "BSMOD: " << (int)getBitStreamMode() << std::endl; + r << std::string(indent + 1, ' ') << "ACMOD: " << (int)getAudioConfigMode() << std::endl; + r << std::string(indent + 1, ' ') << "LFEON: " << (int)getLowFrequencyEffectsChannelOn() << std::endl; + r << std::string(indent + 1, ' ') << "FrameSizeCode: " << (int)getFrameSizeCode() << std::endl; + return r.str(); + } + + FTYP::FTYP(bool fillDefaults){ + memcpy(data + 4, "ftyp", 4); + if (fillDefaults){ + setMajorBrand("mp41"); + setMinorVersion("Mist"); + setCompatibleBrands("isom",0); + setCompatibleBrands("iso2",1); + setCompatibleBrands("avc1",2); + } + } + + void FTYP::setMajorBrand(const char * newMajorBrand){ + if (payloadOffset + 3 >= boxedSize()){ + if ( !reserve(payloadOffset, 0, 4)){ return; } } @@ -996,11 +1248,16 @@ namespace MP4 { return; } } + memset(data + payloadOffset + 4, 0, 4); memcpy(data + payloadOffset + 4, newMinorVersion, 4); } - - std::string FTYP::getMinorVersion() { - return std::string(data + payloadOffset + 4, 4); + + std::string FTYP::getMinorVersion(){ + static char zero[4] = {0,0,0,0}; + if (memcmp(zero, data+payloadOffset+4, 4) == 0){ + return ""; + } + return std::string(data+payloadOffset+4, 4); } size_t FTYP::getCompatibleBrandsCount() { @@ -1035,6 +1292,22 @@ namespace MP4 { return r.str(); } + STYP::STYP(bool fillDefaults) : FTYP(fillDefaults) { + memcpy(data + 4, "styp", 4); + } + + std::string STYP::toPrettyString(uint32_t indent){ + std::stringstream r; + r << std::string(indent, ' ') << "[styp] Fragment Type (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "MajorBrand: " << getMajorBrand() << std::endl; + r << std::string(indent + 1, ' ') << "MinorVersion: " << getMinorVersion() << std::endl; + r << std::string(indent + 1, ' ') << "CompatibleBrands (" << getCompatibleBrandsCount() << "):" << std::endl; + for (unsigned int i = 0; i < getCompatibleBrandsCount(); i++){ + r << std::string(indent + 2, ' ') << "[" << i << "] CompatibleBrand: " << getCompatibleBrands(i) << std::endl; + } + return r.str(); + } + MOOV::MOOV() { memcpy(data + 4, "moov", 4); } @@ -1045,47 +1318,52 @@ namespace MP4 { TREX::TREX() { memcpy(data + 4, "trex", 4); + setTrackID(0); + setDefaultSampleDescriptionIndex(1); + setDefaultSampleDuration(0); + setDefaultSampleSize(0); + setDefaultSampleFlags(0); } - - void TREX::setTrackID(uint32_t newTrackID) { - setInt32(newTrackID, 0); + + void TREX::setTrackID(uint32_t newTrackID){ + setInt32(newTrackID, 4); } - - uint32_t TREX::getTrackID() { - return getInt32(0); - } - - void TREX::setDefaultSampleDescriptionIndex(uint32_t newDefaultSampleDescriptionIndex) { - setInt32(newDefaultSampleDescriptionIndex, 4); - } - - uint32_t TREX::getDefaultSampleDescriptionIndex() { + + uint32_t TREX::getTrackID(){ return getInt32(4); } - - void TREX::setDefaultSampleDuration(uint32_t newDefaultSampleDuration) { - setInt32(newDefaultSampleDuration, 8); + + void TREX::setDefaultSampleDescriptionIndex(uint32_t newDefaultSampleDescriptionIndex){ + setInt32(newDefaultSampleDescriptionIndex,8); } - - uint32_t TREX::getDefaultSampleDuration() { + + uint32_t TREX::getDefaultSampleDescriptionIndex(){ return getInt32(8); } - - void TREX::setDefaultSampleSize(uint32_t newDefaultSampleSize) { - setInt32(newDefaultSampleSize, 12); + + void TREX::setDefaultSampleDuration(uint32_t newDefaultSampleDuration){ + setInt32(newDefaultSampleDuration,12); } - - uint32_t TREX::getDefaultSampleSize() { + + uint32_t TREX::getDefaultSampleDuration(){ return getInt32(12); } - - void TREX::setDefaultSampleFlags(uint32_t newDefaultSampleFlags) { - setInt32(newDefaultSampleFlags, 16); + + void TREX::setDefaultSampleSize(uint32_t newDefaultSampleSize){ + setInt32(newDefaultSampleSize,16); } - - uint32_t TREX::getDefaultSampleFlags() { + + uint32_t TREX::getDefaultSampleSize(){ return getInt32(16); } + + void TREX::setDefaultSampleFlags(uint32_t newDefaultSampleFlags){ + setInt32(newDefaultSampleFlags,20); + } + + uint32_t TREX::getDefaultSampleFlags(){ + return getInt32(20); + } std::string TREX::toPrettyString(uint32_t indent) { std::stringstream r; @@ -1576,28 +1854,28 @@ namespace MP4 { int32_t MVHD::getMatrix(size_t index) { int offset = 0; - if (getVersion() == 0) { - offset = 24 + 2 + 10; - } else { - offset = 36 + 2 + 10; + if (getVersion() == 0){ + offset = 36; + }else{ + offset = 48; } return getInt32(offset + index * 4); } //24 bytes of pre-defined in between - void MVHD::setTrackID(uint32_t newTrackID) { - if (getVersion() == 0) { - setInt32(newTrackID, 86); - } else { - setInt32(newTrackID, 98); + void MVHD::setTrackID(uint32_t newTrackID){ + if (getVersion() == 0){ + setInt32(newTrackID, 96); + }else{ + setInt32(newTrackID, 108); } } - - uint32_t MVHD::getTrackID() { - if (getVersion() == 0) { - return getInt32(86); - } else { - return getInt32(98); + + uint32_t MVHD::getTrackID(){ + if (getVersion() == 0){ + return getInt32(96); + }else{ + return getInt32(108); } } @@ -2775,6 +3053,13 @@ namespace MP4 { return getBox(28); } + /*LTS-START*/ + Box & AudioSampleEntry::getSINFBox() { + static Box ret = Box(getBox(28 + getBoxLen(28)).asBox(), false); + return ret; + } + /*LTS-END*/ + std::string AudioSampleEntry::toPrettyAudioString(uint32_t indent, std::string name) { std::stringstream r; r << std::string(indent, ' ') << name << " (" << boxedSize() << ")" << std::endl; @@ -2784,6 +3069,11 @@ namespace MP4 { r << std::string(indent + 1, ' ') << "PreDefined: " << getPreDefined() << std::endl; r << std::string(indent + 1, ' ') << "SampleRate: " << getSampleRate() << std::endl; r << getCodecBox().toPrettyString(indent + 1) << std::endl; + /*LTS-START*/ + if (isType("enca")) { + r << getSINFBox().toPrettyString(indent + 1); + } + /*LTS-END*/ return r.str(); } @@ -2803,6 +3093,14 @@ namespace MP4 { return toPrettyAudioString(indent, "[aac ] Advanced Audio Codec"); } + HEV1::HEV1() { + memcpy(data + 4, "hev1", 4); + } + + std::string HEV1::toPrettyString(uint32_t indent) { + return toPrettyVisualString(indent, "[hev1] High Efficiency Video Codec 1"); + } + AVC1::AVC1() { memcpy(data + 4, "avc1", 4); } @@ -2819,6 +3117,34 @@ namespace MP4 { return toPrettyVisualString(indent, "[h264] H.264/MPEG-4 AVC"); } + FIEL::FIEL(){ + memcpy(data + 4, "fiel", 4); + } + + void FIEL::setTotal(char newTotal){ + setInt8(newTotal, 0); + } + + char FIEL::getTotal(){ + return getInt8(0); + } + + void FIEL::setOrder(char newOrder){ + setInt8(newOrder, 1); + } + + char FIEL::getOrder(){ + return getInt8(1); + } + + std::string FIEL::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[fiel] Video Field Order Box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Total: " << (int)getTotal() << std::endl; + r << std::string(indent + 1, ' ') << "Order: " << (int)getOrder() << std::endl; + return r.str(); + } + STSD::STSD(char v, uint32_t f) { memcpy(data + 4, "stsd", 4); setVersion(v); @@ -2881,6 +3207,14 @@ namespace MP4 { return r.str(); } + GMHD::GMHD() { + memcpy(data + 4, "gmhd", 4); + } + + TREF::TREF() { + memcpy(data + 4, "tref", 4); + } + EDTS::EDTS() { memcpy(data + 4, "edts", 4); } @@ -3022,3 +3356,4 @@ namespace MP4 { return r.str(); } } + diff --git a/lib/mp4_generic.h b/lib/mp4_generic.h index b11cb7e7..3e1a15ad 100644 --- a/lib/mp4_generic.h +++ b/lib/mp4_generic.h @@ -1,3 +1,4 @@ +#pragma once #include "mp4.h" namespace MP4 { @@ -122,6 +123,56 @@ namespace MP4 { std::string toPrettyString(uint32_t indent = 0); }; + struct HVCCArrayEntry { + char arrayCompleteness; + char nalUnitType; + std::deque nalUnits; + }; + + class HVCC: public Box { + public: + HVCC(); + void setConfigurationVersion(char newVersion); + char getConfigurationVersion(); + void setGeneralProfileSpace(char newGeneral); + char getGeneralProfileSpace(); + void setGeneralTierFlag(char newGeneral); + char getGeneralTierFlag(); + void setGeneralProfileIdc(char newGeneral); + char getGeneralProfileIdc(); + void setGeneralProfileCompatibilityFlags(unsigned long newGeneral); + unsigned long getGeneralProfileCompatibilityFlags(); + void setGeneralConstraintIndicatorFlags(unsigned long long newGeneral); + unsigned long long getGeneralConstraintIndicatorFlags(); + void setGeneralLevelIdc(char newGeneral); + char getGeneralLevelIdc(); + void setMinSpatialSegmentationIdc(short newIdc); + short getMinSpatialSegmentationIdc(); + void setParallelismType(char newType); + char getParallelismType(); + void setChromaFormat(char newFormat); + char getChromaFormat(); + void setBitDepthLumaMinus8(char newBitDepthLumaMinus8); + char getBitDepthLumaMinus8(); + void setBitDepthChromaMinus8(char newBitDepthChromaMinus8); + char getBitDepthChromaMinus8(); + void setAverageFramerate(short newFramerate); + short getAverageFramerate(); + void setConstantFramerate(char newFramerate); + char getConstantFramerate(); + void setNumberOfTemporalLayers(char newNumber); + char getNumberOfTemporalLayers(); + void setTemporalIdNested(char newNested); + char getTemporalIdNested(); + void setLengthSizeMinus1(char newLengthSizeMinus1); + char getLengthSizeMinus1(); + ///\todo Add setter for the array entries + std::deque getArrays(); + std::string asAnnexB(); + void setPayload(std::string newPayload); + std::string toPrettyString(uint32_t indent = 0); + }; + class Descriptor{ public: Descriptor(); @@ -182,9 +233,27 @@ namespace MP4 { std::string toPrettyString(uint32_t indent = 0); }; + class DAC3: public Box { + public: + DAC3(); + char getSampleRateCode();//2bits + void setSampleRateCode(char newVal);//2bits + char getBitStreamIdentification();//5bits + void setBitStreamIdentification(char newVal);//5bits + char getBitStreamMode();//3 bits + void setBitStreamMode(char newVal);//3 bits + char getAudioConfigMode();//3 bits + void setAudioConfigMode(char newVal);//3 bits + bool getLowFrequencyEffectsChannelOn();//1bit + void setLowFrequencyEffectsChannelOn(bool newVal);//1bit + char getFrameSizeCode();//6bits + void setFrameSizeCode(char newVal);//6bits + std::string toPrettyString(uint32_t indent = 0); + }; + class FTYP: public Box { public: - FTYP(); + FTYP(bool fillDefaults = true); void setMajorBrand(const char * newMajorBrand); std::string getMajorBrand(); void setMinorVersion(const char * newMinorVersion); @@ -195,6 +264,12 @@ namespace MP4 { std::string toPrettyString(uint32_t indent = 0); }; + class STYP: public FTYP { + public: + STYP(bool fillDefaults = true); + std::string toPrettyString(uint32_t indent = 0); + }; + class MOOV: public containerBox { public: MOOV(); @@ -205,7 +280,7 @@ namespace MP4 { MVEX(); }; - class TREX: public Box { + class TREX: public fullBox { public: TREX(); void setTrackID(uint32_t newTrackID); @@ -600,6 +675,7 @@ namespace MP4 { uint32_t getSampleRate(); void setCodecBox(Box & newBox); Box & getCodecBox(); + Box & getSINFBox(); /*LTS*/ std::string toPrettyAudioString(uint32_t indent = 0, std::string name = ""); }; @@ -615,6 +691,12 @@ namespace MP4 { std::string toPrettyString(uint32_t indent = 0); }; + class HEV1: public VisualSampleEntry { + public: + HEV1(); + std::string toPrettyString(uint32_t indent = 0); + }; + class AVC1: public VisualSampleEntry { public: AVC1(); @@ -627,6 +709,16 @@ namespace MP4 { std::string toPrettyString(uint32_t indent = 0); }; + class FIEL: public Box { + public: + FIEL(); + void setTotal(char newTotal); + char getTotal(); + void setOrder(char newOrder); + char getOrder(); + std::string toPrettyString(uint32_t indent = 0); + }; + class STSD: public fullBox { public: STSD(char v = 1, uint32_t f = 0); @@ -637,6 +729,16 @@ namespace MP4 { std::string toPrettyString(uint32_t indent = 0); }; + class GMHD: public containerBox { + public: + GMHD(); + }; + + class TREF: public containerBox { + public: + TREF(); + }; + class EDTS: public containerBox { public: EDTS(); @@ -677,3 +779,4 @@ namespace MP4 { std::string toPrettyString(uint32_t indent = 0); }; } + diff --git a/lib/mp4_ms.cpp b/lib/mp4_ms.cpp index 47424428..0247ac0b 100644 --- a/lib/mp4_ms.cpp +++ b/lib/mp4_ms.cpp @@ -1,4 +1,5 @@ #include "mp4_ms.h" +#include "mp4_encryption.h" /*LTS*/ namespace MP4 { @@ -127,6 +128,17 @@ namespace MP4 { if (UUID == "d4807ef2-ca39-4695-8e54-26cb9e46a79f") { return ((UUID_TrackFragmentReference *)this)->toPrettyString(indent); } + /*LTS-START*/ + if (UUID == "a2394f52-5a9b-4f14-a244-6c427c648df4") { + return ((UUID_SampleEncryption *)this)->toPrettyString(indent); + } + if (UUID == "8974dbce-7be7-4c51-84f9-7148f9882554") { + return ((UUID_TrackEncryption *)this)->toPrettyString(indent); + } + if (UUID == "d08a4f18-10f3-4a82-b6c8-32d8aba183d3") { + return ((UUID_ProtectionSystemSpecificHeader *)this)->toPrettyString(indent); + } + /*LTS-END*/ std::stringstream r; r << std::string(indent, ' ') << "[uuid] Extension box (" << boxedSize() << ")" << std::endl; r << std::string(indent + 1, ' ') << "UUID: " << UUID << std::endl; diff --git a/lib/rtp.cpp b/lib/rtp.cpp new file mode 100644 index 00000000..429a06f1 --- /dev/null +++ b/lib/rtp.cpp @@ -0,0 +1,239 @@ +#include +#include "rtp.h" +#include "timing.h" +#include "defines.h" + +#define MAX_SEND 1024*4 + +namespace RTP { + double Packet::startRTCP = 0; + + unsigned int Packet::getHsize() const { + return 12 + 4 * getContribCount(); + } + + unsigned int Packet::getVersion() const { + return (data[0] >> 6) & 0x3; + } + + unsigned int Packet::getPadding() const { + return (data[0] >> 5) & 0x1; + } + + unsigned int Packet::getExtension() const { + return (data[0] >> 4) & 0x1; + } + + unsigned int Packet::getContribCount() const { + return (data[0]) & 0xE; + } + + unsigned int Packet::getMarker() const { + return (data[1] >> 7) & 0x1; + } + + unsigned int Packet::getPayloadType() const { + return (data[1]) & 0x7F; + } + + unsigned int Packet::getSequence() const { + return (((((unsigned int)data[2]) << 8) + data[3])); + } + + unsigned int Packet::getTimeStamp() const { + return ntohl(*((unsigned int *)(data + 4))); + } + + unsigned int Packet::getSSRC() const { + return ntohl(*((unsigned int *)(data + 8))); + } + + char * Packet::getData() { + return data + 8 + 4 * getContribCount() + getExtension(); + } + + void Packet::setTimestamp(unsigned int t) { + *((unsigned int *)(data + 4)) = htonl(t); + } + + void Packet::setSequence(unsigned int seq) { + *((short *)(data + 2)) = htons(seq); + } + + void Packet::setSSRC(unsigned long ssrc) { + *((int *)(data + 8)) = htonl(ssrc); + } + + void Packet::increaseSequence() { + *((short *)(data + 2)) = htons(getSequence() + 1); + } + + void Packet::sendH264(void * socket, void callBack(void *, char *, unsigned int, unsigned int), const char * payload, unsigned int payloadlen, unsigned int channel) { + /// \todo This function probably belongs in DMS somewhere. + if (payloadlen <= MAX_SEND) { + data[1] |= 0x80;//setting the RTP marker bit to 1 + memcpy(data + getHsize(), payload, payloadlen); + callBack(socket, data, getHsize() + payloadlen, channel); + sentPackets++; + sentBytes += payloadlen; + increaseSequence(); + } else { + data[1] &= 0x7F;//setting the RTP marker bit to 0 + unsigned int sent = 0; + unsigned int sending = MAX_SEND;//packages are of size MAX_SEND, except for the final one + char initByte = (payload[0] & 0xE0) | 0x1C; + char serByte = payload[0] & 0x1F; //ser is now 000 + data[getHsize()] = initByte; + while (sent < payloadlen) { + if (sent == 0) { + serByte |= 0x80;//set first bit to 1 + } else { + serByte &= 0x7F;//set first bit to 0 + } + if (sent + MAX_SEND >= payloadlen) { + //last package + serByte |= 0x40; + sending = payloadlen - sent; + data[1] |= 0x80;//setting the RTP marker bit to 1 + } + data[getHsize() + 1] = serByte; + memcpy(data + getHsize() + 2, payload + 1 + sent, sending); //+1 because + callBack(socket, data, getHsize() + 2 + sending, channel); + sentPackets++; + sentBytes += sending; + sent += sending; + increaseSequence(); + } + } + } + + void Packet::sendAAC(void * socket, void callBack(void *, char *, unsigned int, unsigned int), const char * payload, unsigned int payloadlen, unsigned int channel) { + /// \todo This function probably belongs in DMS somewhere. + data[1] |= 0x80;//setting the RTP marker bit to 1 + /// \todo This 0x100000 value - What is it? Why is it hardcoded? + /// \todo The least significant 3 bits are used to signal some stuff from RFC 3640. Why do we send them always as 000? + *((int *)(data + getHsize())) = htonl(((payloadlen << 3) & 0x0010fff8) | 0x00100000); + memcpy(data + getHsize() + 4, payload, payloadlen); + callBack(socket, data, getHsize() + 4 + payloadlen, channel); + sentPackets++; + sentBytes += payloadlen; + increaseSequence(); + } + + void Packet::sendRaw(void * socket, void callBack(void *, char *, unsigned int, unsigned int), const char * payload, unsigned int payloadlen, unsigned int channel) { + /// \todo This function probably belongs in DMS somewhere. + data[1] |= 0x80;//setting the RTP marker bit to 1 + memcpy(data + getHsize(), payload, payloadlen); + callBack(socket, data, getHsize() + payloadlen, channel); + sentPackets++; + sentBytes += payloadlen; + increaseSequence(); + } + +/// Stores a long long (64 bits) value of val in network order to the pointer p. + inline void Packet::htobll(char * p, long long val) { + p[0] = (val >> 56) & 0xFF; + p[1] = (val >> 48) & 0xFF; + p[2] = (val >> 40) & 0xFF; + p[3] = (val >> 32) & 0xFF; + p[4] = (val >> 24) & 0xFF; + p[5] = (val >> 16) & 0xFF; + p[6] = (val >> 8) & 0xFF; + p[7] = val & 0xFF; + } + + + + void Packet::sendRTCP(long long & connectedAt, void * socket, unsigned int tid , DTSC::Meta & metadata, void callBack(void *, char *, unsigned int, unsigned int)) { + void * rtcpData = malloc(32); + if (!rtcpData){ + FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up."); + return; + } + ((int *)rtcpData)[0] = htonl(0x80C80006); + ((int *)rtcpData)[1] = htonl(getSSRC()); + // unsigned int tid = packet["trackid"].asInt(); + //timestamp in ms + double ntpTime = 2208988800UL + Util::epoch() + (Util::getMS() % 1000) / 1000.0; + if (startRTCP < 1 && startRTCP > -1) { + startRTCP = ntpTime; + } + ntpTime -= startRTCP; + + ((int *)rtcpData)[2] = htonl(2208988800UL + Util::epoch()); //epoch is in seconds + ((int *)rtcpData)[3] = htonl((Util::getMS() % 1000) * 4294967.295); + if (metadata.tracks[tid].codec == "H264") { + ((int *)rtcpData)[4] = htonl((ntpTime - 0) * 90000); //rtpts + } else if (metadata.tracks[tid].codec == "AAC") { + ((int *)rtcpData)[4] = htonl((ntpTime - 0) * metadata.tracks[tid].rate); //rtpts + } else { + DEBUG_MSG(DLVL_FAIL, "Unsupported codec"); + return; + } + //it should be the time packet was sent maybe, after all? + //*((int *)(rtcpData+16) ) = htonl(getTimeStamp());//rtpts + ((int *)rtcpData)[5] = htonl(sentPackets);//packet + ((int *)rtcpData)[6] = htonl(sentBytes);//octet + callBack(socket, (char*)rtcpData , 28 , 0); + free(rtcpData); + } + + Packet::Packet() { + managed = false; + data = 0; + } + + Packet::Packet(unsigned int payloadType, unsigned int sequence, unsigned int timestamp, unsigned int ssrc, unsigned int csrcCount) { + managed = true; + data = new char[12 + 4 * csrcCount + 2 + MAX_SEND]; //headerSize, 2 for FU-A, MAX_SEND for maximum sent size + data[0] = ((2) << 6) | ((0 & 1) << 5) | ((0 & 1) << 4) | (csrcCount & 15); //version, padding, extension, csrc count + data[1] = payloadType & 0x7F; //marker and payload type + setSequence(sequence - 1); //we automatically increase the sequence each time when p + setTimestamp(timestamp); + setSSRC(ssrc); + sentBytes = 0; + sentPackets = 0; + } + + Packet::Packet(const Packet & o) { + managed = true; + if (o.data) { + data = new char[o.getHsize() + 2 + MAX_SEND]; //headerSize, 2 for FU-A, MAX_SEND for maximum sent size + if (data) { + memcpy(data, o.data, o.getHsize() + 2 + MAX_SEND); + } + } else { + data = new char[14 + MAX_SEND];//headerSize, 2 for FU-A, MAX_SEND for maximum sent size + if (data) { + memset(data, 0, 14 + MAX_SEND); + } + } + sentBytes = o.sentBytes; + sentPackets = o.sentPackets; + } + + void Packet::operator=(const Packet & o) { + managed = true; + if (data) { + delete[] data; + } + data = new char[o.getHsize() + 2 + MAX_SEND]; + if (data) { + memcpy(data, o.data, o.getHsize() + 2 + MAX_SEND); + } + sentBytes = o.sentBytes; + sentPackets = o.sentPackets; + } + + Packet::~Packet() { + if (managed) { + delete [] data; + } + } + Packet::Packet(const char * dat, unsigned int len) { + managed = false; + datalen = len; + data = (char *) dat; + } + +} diff --git a/lib/rtp.h b/lib/rtp.h new file mode 100644 index 00000000..835ea170 --- /dev/null +++ b/lib/rtp.h @@ -0,0 +1,62 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "socket.h" +#include "json.h" +#include "dtsc.h" +#include "mp4.h" +#include "mp4_generic.h" + +/// This namespace holds all RTP-parsing and sending related functionality. +namespace RTP { + + /// This class is used to make RTP packets. Currently, H264, and AAC are supported. RTP mechanisms, like increasing sequence numbers and setting timestamps are all taken care of in here. + class Packet { + private: + bool managed; + char * data; ///mFunction(ti->mArg); - } catch (...) { + //} catch (...) { // Uncaught exceptions will terminate the application (default behavior // according to C++11) - std::terminate(); - } + //std::terminate(); + //} // The thread is no longer executing if (ti->mThread) { diff --git a/lib/ts_packet.cpp b/lib/ts_packet.cpp index b943445b..ddf5403c 100644 --- a/lib/ts_packet.cpp +++ b/lib/ts_packet.cpp @@ -1022,10 +1022,14 @@ namespace TS { for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ if (myMeta.tracks[*it].codec == "H264"){ PMT.setStreamType(0x1B,id); + }else if (myMeta.tracks[*it].codec == "HEVC"){ + PMT.setStreamType(0x06,id); }else if (myMeta.tracks[*it].codec == "AAC"){ PMT.setStreamType(0x0F,id); }else if (myMeta.tracks[*it].codec == "MP3"){ PMT.setStreamType(0x03,id); + }else if (myMeta.tracks[*it].codec == "AC3"){ + PMT.setStreamType(0x81,id); } PMT.setElementaryPID(0x100 + (*it) - 1, id); PMT.setESInfoLength(0,id); diff --git a/src/analysers/info.cpp b/src/analysers/info.cpp index 4b01a9c9..f3687bc3 100644 --- a/src/analysers/info.cpp +++ b/src/analysers/info.cpp @@ -96,6 +96,7 @@ namespace Info { fileSpecs["tracks"][trackIt->first].removeMember("keys"); fileSpecs["tracks"][trackIt->first].removeMember("keysizes"); fileSpecs["tracks"][trackIt->first].removeMember("parts"); + fileSpecs["tracks"][trackIt->first].removeMember("ivecs");/*LTS*/ } } printf( "%s", fileSpecs.toString().c_str() ); diff --git a/src/analysers/mp4_analyser.cpp b/src/analysers/mp4_analyser.cpp index dcce50fa..fa322420 100644 --- a/src/analysers/mp4_analyser.cpp +++ b/src/analysers/mp4_analyser.cpp @@ -9,6 +9,7 @@ #include #include #include +#include ///\brief Holds everything unique to the analysers. namespace Analysers { @@ -25,9 +26,15 @@ namespace Analysers { mp4Buffer.erase(mp4Buffer.size() - 1, 1); MP4::Box mp4Data; + int dataSize = mp4Buffer.size(); + int curPos = 0; while (mp4Data.read(mp4Buffer)){ + DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos); std::cerr << mp4Data.toPrettyString(0) << std::endl; + curPos += dataSize - mp4Buffer.size(); + dataSize = mp4Buffer.size(); } + DEBUG_MSG(DLVL_DEVEL, "Stopped parsing at position %d", curPos); return 0; } } diff --git a/src/analysers/rtp_analyser.cpp b/src/analysers/rtp_analyser.cpp new file mode 100644 index 00000000..239a9f72 --- /dev/null +++ b/src/analysers/rtp_analyser.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//rtsp://krabs:1935/vod/gear1.mp4 + +namespace Analysers { + int analyseRTP(){ + Socket::Connection conn("localhost", 554, true); + //Socket::Connection conn("krabs", 1935, true); + HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. + int step = 0; + /*1 = sent describe + 2 = recd describe + 3 = sent setup + 4 = received setup + 5 = sent play"*/ + std::vector tracks; + std::vector connections; + unsigned int trackIt = 0; + while (conn.connected()){ + // std::cerr << "loopy" << std::endl; + if(step == 0){ + HTTP_S.protocol = "RTSP/1.0"; + HTTP_S.method = "DESCRIBE"; + //rtsp://krabs:1935/vod/gear1.mp4 + //rtsp://localhost/g1 + HTTP_S.url = "rtsp://localhost/steers"; + //HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4"; + HTTP_S.SetHeader("CSeq",1); + HTTP_S.SendRequest(conn); + step++; + + }else if(step == 2){ + std::cerr <<"setup " << tracks[trackIt] << std::endl; + HTTP_S.method = "SETUP"; + HTTP_S.url = "rtsp://localhost/steers/" + tracks[trackIt]; + //HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4/" + tracks[trackIt]; + HTTP_S.SetHeader("CSeq",2+trackIt); + std::stringstream ss; + ss << "RTP/steersVP;unicast;client_port="<< 20000 + 2*trackIt<<"-"<< 20001 + 2*trackIt; + HTTP_S.SetHeader("Transport",ss.str());//make client ports, 4200 + 2*offset + trackIt++; + step++; + HTTP_S.SendRequest(conn); + std::cerr << "step " << step << "/\\"<< ss.str()<getPayloadType() == 97){ + int h264type = (int)(connections[cx].data[12] & 0x1f); + std::cout << h264type << " - "; + if(h264type == 0){ + std::cout << "unspecified - "; + }else if(h264type == 1){ + std::cout << "Coded slice of a non-IDR picture - "; + }else if(h264type == 2){ + std::cout << "Coded slice data partition A - "; + }else if(h264type == 3){ + std::cout << "Coded slice data partition B - "; + }else if(h264type == 4){ + std::cout << "Coded slice data partition C - "; + }else if(h264type == 5){ + std::cout << "Coded slice of an IDR picture - "; + }else if(h264type == 6){ + std::cout << "Supplemental enhancement information (SEI) - "; + }else if(h264type == 7){ + std::cout << "Sequence parameter set - "; + }else if(h264type == 8){ + std::cout << "Picture parameter set - "; + }else if(h264type == 9){ + std::cout << "Access unit delimiter - "; + }else if(h264type == 10){ + std::cout << "End of sequence - "; + }else if(h264type == 11){ + std::cout << "End of stream - "; + }else if(h264type == 12){ + std::cout << "Filler data - "; + }else if(h264type == 13){ + std::cout << "Sequence parameter set extension - "; + }else if(h264type == 14){ + std::cout << "Prefix NAL unit - "; + }else if(h264type == 15){ + std::cout << "Subset sequence parameter set - "; + }else if(h264type == 16){ + std::cout << "Reserved - "; + }else if(h264type == 17){ + std::cout << "Reserved - "; + }else if(h264type == 18){ + std::cout << "Reserved - "; + }else if(h264type == 19){ + std::cout << "Coded slice of an auxiliary coded picture without partitioning - "; + }else if(h264type == 20){ + std::cout << "Coded slice extension - "; + }else if(h264type == 21){ + std::cout << "Reserved - "; + }else if(h264type == 22){ + std::cout << "Reserved - "; + }else if(h264type == 23){ + std::cout << "Reserved - "; + }else if(h264type == 24){ + std::cout << "stap a - "; + }else if(h264type == 25){ + std::cout << "stap b - "; + }else if(h264type == 26){ + std::cout << "mtap16 - "; + }else if(h264type == 27){ + std::cout << "mtap24 - "; + }else if(h264type == 28){ + std::cout << "fu a - "; + }else if(h264type == 29){ + std::cout << "fu b - "; + }else if(h264type == 30){ + std::cout << "Unspecified - "; + }else if(h264type == 31){ + std::cout << "Unspecified - "; + } + + + + + for(unsigned int i = 13 ; i < connections[cx].data_len;i++){ + std::cout << std::hex < +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace RtspRtp{ + + int analyseRtspRtp(std::string rtspUrl){ + /*//parse hostname + std::string hostname = rtspUrl.substr(7); + hostname = hostname.substr(0,hostname.find('/')); + std::cout << hostname << std::endl; + HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. + Socket::Connection conn(hostname,554,false);//setting rtsp connection + + bool optionsSent = false; + bool optionsRecvd = false; + bool descSent = false; + bool descRecvd = false; + bool setupComplete = false; + bool playSent = false; + int CSeq = 1; + while(conn.connected()){ + if(!optionsSent){ + HTTP_R.protocol="RTSP/1.0"; + HTTP_R.method = "OPTIONS"; + HTTP_R.url = rtspUrl; + HTTP_R.SetHeader("CSeq",CSeq); + CSeq++; + HTTP_R.SetHeader("User-Agent","mistANALyser"); + HTTP_R.SendRequest(conn); + optionsSent = true; + } + + if (optionsSent&& !optionsRecvd && (conn.Received().size() || conn.spool() )){ + if(HTTP_S.Read(conn)){ + std::cout << "recv opts" << std::endl; + + std::cout << HTTP_S.BuildResponse(HTTP_S.method,HTTP_S.url); + optionsRecvd = true; + } + } + + + + if(optionsRecvd && !descSent){ + HTTP_S.Clean(); + HTTP_R.protocol="RTSP/1.0"; + HTTP_R.method = "DESCRIBE"; + HTTP_R.url = rtspUrl; + HTTP_R.SetHeader("CSeq",CSeq); + CSeq++; + HTTP_R.SetHeader("User-Agent","mistANALyser"); + HTTP_R.SendRequest(conn); + descSent = true; + + } + + std::vector trackIds; + + if (descSent&&!descRecvd && (conn.Received().size() || conn.spool() )){ + + if(HTTP_S.Read(conn)){ + std::cout << "recv desc2" << std::endl; + std::cout << HTTP_S.BuildResponse(HTTP_S.method,HTTP_S.url); + size_t pos = HTTP_S.body.find("m="); + do{ + //finding all track IDs + pos = HTTP_S.body.find("a=control:",pos); + if(pos !=std::string::npos){ + trackIds.push_back(HTTP_S.body.substr(pos+10,HTTP_S.body.find("\r\n",pos)-pos-10 ) );//setting track IDs; + pos++; + } + }while(pos != std::string::npos); + //we have all the tracks + + descRecvd = true; + } + } + + + unsigned int setupsSent = 0; + unsigned int setupsRecvd = 0; + Socket::UDPConnection connectors[trackIds.size()]; + unsigned int setports[trackIds.size()]; + uint32_t bport = 10000; + std::string sessionID = ""; + + std::stringstream setup; + + if(descRecvd && !setupComplete){ + //time to setup. + for(std::vector::iterator it = trackIds.begin();it!=trackIds.end();it++){ + std::cout << "setup " << setupsSent<< std::endl; + while(!connectors[setupsSent].SetConnection( bport,false) ){ + bport +=2;//finding an available port + } + std::cout << "setup" << bport<< std::endl; + setports[setupsSent] = bport; + bport +=2; + if(setupsSent == setupsRecvd){ + //send only one setup + HTTP_S.Clean(); + HTTP_R.protocol="RTSP/1.0"; + HTTP_R.method = "SETUP"; + HTTP_R.url = rtspUrl+ '/' + *(it); + setup << "RTP/AVP/UDP;unicast;client_port="<< setports[setupsSent] <<"-" < + +#include +#include +#include +#include +#include + +/// Will emulate a given amount of clients in the statistics. +int main(int argc, char ** argv){ + Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); + conf.addOption("clients", JSON::fromString("{\"arg\":\"num\", \"short\":\"c\", \"long\":\"clients\", \"default\":1000, \"help\":\"Amount of clients to emulate.\"}")); + conf.addOption("stream", JSON::fromString("{\"arg\":\"string\", \"short\":\"s\", \"long\":\"stream\", \"default\":\"test\", \"help\":\"Streamname to pretend to request.\"}")); + conf.addOption("up", JSON::fromString("{\"arg\":\"string\", \"short\":\"u\", \"long\":\"up\", \"default\":131072, \"help\":\"Bytes per second upstream.\"}")); + conf.addOption("down", JSON::fromString("{\"arg\":\"string\", \"short\":\"d\", \"long\":\"down\", \"default\":13000, \"help\":\"Bytes per second downstream.\"}")); + conf.addOption("sine", JSON::fromString("{\"arg\":\"string\", \"short\":\"S\", \"long\":\"sine\", \"default\":0, \"help\":\"Bytes per second variance in a sine pattern.\"}")); + conf.addOption("userscale", JSON::fromString("{\"arg\":\"string\", \"short\":\"U\", \"long\":\"userscale\", \"default\":0, \"help\":\"If != 0, scales users from 0% to 100% bandwidth.\"}")); + conf.parseArgs(argc, argv); + + std::string streamName = conf.getString("stream"); + long long clientCount = conf.getInteger("clients"); + long long up = conf.getInteger("up"); + long long down = conf.getInteger("down"); + long long sine = conf.getInteger("sine"); + long long scale = conf.getInteger("userscale"); + long long currsine = sine; + long long goingUp = 0; + + IPC::sharedClient ** clients = (IPC::sharedClient **)malloc(sizeof(IPC::sharedClient *)*clientCount); + for (long long i = 0; i < clientCount; i++){ + clients[i] = new IPC::sharedClient("statistics", STAT_EX_SIZE, true); + } + + unsigned long long int counter = 0; + conf.activate(); + + while (conf.is_active){ + unsigned long long int now = Util::epoch(); + counter++; + if (sine){ + currsine += goingUp; + if (currsine < -down || currsine < -up){ + currsine = std::max(-down, -up); + } + if (currsine > 0){ + goingUp -= sine/100 + 1; + }else{ + goingUp += sine/100 + 1; + } + } + for (long long i = 0; i < clientCount; i++){ + if (clients[i]->getData()){ + IPC::statExchange tmpEx(clients[i]->getData()); + tmpEx.now(now); + tmpEx.host("::42"); + tmpEx.crc(i); + tmpEx.streamName(streamName); + tmpEx.connector("TEST"); + if (scale){ + tmpEx.up(tmpEx.up() + (up+currsine)*i/clientCount); + tmpEx.down(tmpEx.down() + (down+currsine)*i/clientCount); + }else{ + tmpEx.up(tmpEx.up()+up+currsine); + tmpEx.down(tmpEx.down()+down+currsine); + } + tmpEx.time(counter); + tmpEx.lastSecond(counter * 1000); + clients[i]->keepAlive(); + } + } + Util::wait(1000); + } + + for (long long i = 0; i < clientCount; i++){ + clients[i]->finish(); + delete clients[i]; + } + + free(clients); + return 0; +} diff --git a/src/analysers/ts_analyser.cpp b/src/analysers/ts_analyser.cpp new file mode 100755 index 00000000..9796bdab --- /dev/null +++ b/src/analysers/ts_analyser.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Analysers { + std::string printPES(const std::string & d, unsigned long PID, int detailLevel){ + unsigned int headSize = 0; + std::stringstream res; + bool known = false; + res << "[PES " << PID << "]"; + if ((d[3] & 0xF0) == 0xE0){ + res << " [Video " << (int)(d[3] & 0xF) << "]"; + known = true; + } + if (!known && (d[3] & 0xE0) == 0xC0){ + res << " [Audio " << (int)(d[3] & 0x1F) << "]"; + known = true; + } + if (!known){ + res << " [Unknown stream ID]"; + } + if (d[0] != 0 || d[1] != 0 || d[2] != 1){ + res << " [!INVALID START CODE!]"; + } + if (known){ + if ((d[6] & 0xC0) != 0x80){ + res << " [!INVALID FIRST BITS!]"; + } + if (d[6] & 0x30){ + res << " [SCRAMBLED]"; + } + if (d[6] & 0x08){ + res << " [Priority]"; + } + if (d[6] & 0x04){ + res << " [Aligned]"; + } + if (d[6] & 0x02){ + res << " [Copyrighted]"; + } + if (d[6] & 0x01){ + res << " [Original]"; + }else{ + res << " [Copy]"; + } + + if (d[7] & 0x20){ + res << " [ESCR present, not decoded!]"; + headSize += 6; + } + if (d[7] & 0x10){ + res << " [ESR present, not decoded!]"; + headSize += 3; + } + if (d[7] & 0x08){ + res << " [Trick mode present, not decoded!]"; + headSize += 1; + } + if (d[7] & 0x04){ + res << " [Add. copy present, not decoded!]"; + headSize += 1; + } + if (d[7] & 0x02){ + res << " [CRC present, not decoded!]"; + headSize += 2; + } + if (d[7] & 0x01){ + res << " [Extension present, not decoded!]"; + headSize += 0; /// \todo Implement this. Complicated field, bah. + } + int timeFlags = ((d[7] & 0xC0) >> 6); + if (timeFlags == 2){ + headSize += 5; + } + if (timeFlags == 3){ + headSize += 10; + } + if (d[8] != headSize){ + res << " [Padding: " << ((int)d[8] - headSize) << "b]"; + } + if (timeFlags & 0x02){ + long long unsigned int time = (((unsigned int)d[9] & 0xE) >> 1); + time <<= 15; + time |= ((unsigned int)d[10] << 7) | (((unsigned int)d[11] >> 1) & 0x7F); + time <<= 15; + time |= ((unsigned int)d[12] << 7) | (((unsigned int)d[13] >> 1) & 0x7F); + res << " [PTS " << ((double)time / 90000) << "s]"; + } + if (timeFlags & 0x01){ + long long unsigned int time = ((d[14] >> 1) & 0x07); + time <<= 15; + time |= ((int)d[15] << 7) | (d[16] >> 1); + time <<= 15; + time |= ((int)d[17] << 7) | (d[18] >> 1); + res << " [DTS " << ((double)time/90000) << "s]"; + } + } + if ((((int)d[4]) << 8 | d[5]) != (d.size() - 6)){ + res << " [Size " << (((int)d[4]) << 8 | d[5]) << " => " << (d.size() - 6) << "]"; + } + res << std::endl; + + if(detailLevel==1){ + unsigned int counter = 0; + for (unsigned int i = 9+headSize; i payloads; + TS::Packet packet; + long long int upTime = Util::bootSecs(); + int64_t pcr = 0; + unsigned int bytes = 0; + char packetPtr[188]; + while (std::cin.good()){ + std::cin.read(packetPtr,188); + if(std::cin.gcount() != 188){break;} + bytes += 188; + if(packet.FromPointer(packetPtr)){ + if(analyse){ + if (packet.getUnitStart() && payloads[packet.getPID()] != ""){ + std::cout << printPES(payloads[packet.getPID()], packet.getPID(), detailLevel); + payloads.erase(packet.getPID()); + } + if (detailLevel < 2){ + std::stringstream nul; + nul << packet.toPrettyString(0, detailLevel); + }else{ + std::cout << packet.toPrettyString(0, detailLevel); + } + if (packet.getPID() && !packet.isPMT()){ + payloads[packet.getPID()].append(packet.getPayload(), packet.getPayloadLength()); + } + } + if(packet && packet.getAdaptationField() > 1 && packet.hasPCR()){pcr = packet.getPCR();} + } + if(bytes > 1024){ + long long int tTime = Util::bootSecs(); + if(validate && tTime - upTime > 5 && tTime - upTime > pcr/27000000){ + std::cerr << "data received too slowly" << std::endl; + return 1; + } + bytes = 0; + } + } + for (std::map::iterator it = payloads.begin(); it != payloads.end(); it++){ + if (!it->first || it->first == 4096){ continue; } + std::cout << printPES(it->second, it->first, detailLevel); + } + long long int finTime = Util::bootSecs(); + if(validate){ + fprintf(stdout,"time since boot,time at completion,real time duration of data receival,video duration\n"); + fprintf(stdout, "%lli000,%lli000,%lli000,%li \n",upTime,finTime,finTime-upTime,pcr/27000); + } + return 0; + } +} + +int main(int argc, char ** argv){ + Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); + conf.addOption("analyse", JSON::fromString("{\"long\":\"analyse\", \"short\":\"a\", \"default\":1, \"long_off\":\"notanalyse\", \"short_off\":\"b\", \"help\":\"Analyse a file's contents (-a), or don't (-b) returning false on error. Default is analyse.\"}")); + conf.addOption("validate", JSON::fromString("{\"long\":\"validate\", \"short\":\"V\", \"default\":0, \"long_off\":\"notvalidate\", \"short_off\":\"X\", \"help\":\"Validate (-V) the file contents or don't validate (-X) its integrity, returning false on error. Default is don't validate.\"}")); + conf.addOption("detail", JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":3, \"help\":\"Detail level of analysis.\"}")); + conf.parseArgs(argc, argv); + return Analysers::analyseTS(conf.getBool("validate"),conf.getBool("analyse"),conf.getInteger("detail")); +} diff --git a/src/controller/controller.cpp b/src/controller/controller.cpp index 2f2063af..593692d0 100644 --- a/src/controller/controller.cpp +++ b/src/controller/controller.cpp @@ -46,6 +46,11 @@ #include "controller_capabilities.h" #include "controller_connectors.h" #include "controller_statistics.h" +/*LTS-START*/ +#include "controller_updater.h" +#include "controller_limits.h" +#include "controller_uplink.h" +/*LTS-END*/ #include "controller_api.h" #ifndef COMPILED_USERNAME @@ -88,7 +93,19 @@ void createAccount (std::string account){ /// Status monitoring thread. /// Will check outputs, inputs and converters every five seconds void statusMonitor(void * np){ + #ifdef UPDATER + unsigned long updatechecker = Util::epoch(); /*LTS*/ + #endif while (Controller::conf.is_active){ + /*LTS-START*/ + #ifdef UPDATER + if (Util::epoch() - updatechecker > 3600){ + updatechecker = Util::epoch(); + Controller::CheckUpdateInfo(); + } + #endif + /*LTS-END*/ + //this scope prevents the configMutex from being locked constantly { tthread::lock_guard guard(Controller::configMutex); @@ -127,6 +144,12 @@ int main(int argc, char ** argv){ Controller::conf.addOption("account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}")); Controller::conf.addOption("logfile", JSON::fromString("{\"long\":\"logfile\", \"short\":\"L\", \"arg\":\"string\" \"default\":\"\",\"help\":\"Redirect all standard output to a log file, provided with an argument\"}")); Controller::conf.addOption("configFile", JSON::fromString("{\"long\":\"config\", \"short\":\"c\", \"arg\":\"string\" \"default\":\"config.json\", \"help\":\"Specify a config file other than default.\"}")); + #ifdef UPDATER + Controller::conf.addOption("update", JSON::fromString("{\"default\":0, \"help\":\"Check for and install updates before starting.\", \"short\":\"D\", \"long\":\"update\"}")); /*LTS*/ + #endif + Controller::conf.addOption("uplink", JSON::fromString("{\"default\":\"\", \"arg\":\"string\", \"help\":\"MistSteward uplink host and port.\", \"short\":\"U\", \"long\":\"uplink\"}")); /*LTS*/ + Controller::conf.addOption("uplink-name", JSON::fromString("{\"default\":\"" COMPILED_USERNAME "\", \"arg\":\"string\", \"help\":\"MistSteward uplink username.\", \"short\":\"N\", \"long\":\"uplink-name\"}")); /*LTS*/ + Controller::conf.addOption("uplink-pass", JSON::fromString("{\"default\":\"" COMPILED_PASSWORD "\", \"arg\":\"string\", \"help\":\"MistSteward uplink password.\", \"short\":\"P\", \"long\":\"uplink-pass\"}")); /*LTS*/ Controller::conf.parseArgs(argc, argv); if(Controller::conf.getString("logfile")!= ""){ //open logfile, dup stdout to logfile @@ -246,14 +269,29 @@ int main(int argc, char ** argv){ Controller::Log("CONF", "Controller started"); Controller::conf.activate();//activate early, so threads aren't killed. + /*LTS-START*/ + #ifdef UPDATER + if (Controller::conf.getBool("update")){ + Controller::CheckUpdates(); + } + #endif + /*LTS-END*/ + //start stats thread tthread::thread statsThread(Controller::SharedMemStats, &Controller::conf); //start monitoring thread tthread::thread monitorThread(statusMonitor, 0); + //start monitoring thread /*LTS*/ + tthread::thread uplinkThread(Controller::uplinkConnection, 0);/*LTS*/ //start main loop Controller::conf.serveThreadedSocket(Controller::handleAPIConnection); //print shutdown reason + /*LTS-START*/ + if (Controller::restarting){ + Controller::Log("CONF", "Controller restarting for update"); + } + /*LTS-END*/ if (!Controller::conf.is_active){ Controller::Log("CONF", "Controller shutting down because of user request (received shutdown signal)"); }else{ @@ -263,6 +301,7 @@ int main(int argc, char ** argv){ //join all joinable threads statsThread.join(); monitorThread.join(); + uplinkThread.join();/*LTS*/ //give everything some time to print messages Util::wait(100); //close stderr to make the stderr reading thread exit @@ -282,5 +321,12 @@ int main(int argc, char ** argv){ //stop all child processes Util::Procs::StopAll(); std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl; + /*LTS-START*/ + if (Controller::restarting){ + std::string myFile = Util::getMyPath() + "MistController"; + execvp(myFile.c_str(), argv); + std::cout << "Error restarting: " << strerror(errno) << std::endl; + } + /*LTS-END*/ return 0; } diff --git a/src/controller/controller_api.cpp b/src/controller/controller_api.cpp index ab21cc1d..2ea8fe6f 100644 --- a/src/controller/controller_api.cpp +++ b/src/controller/controller_api.cpp @@ -11,6 +11,10 @@ #include "controller_connectors.h" #include "controller_capabilities.h" #include "controller_statistics.h" +/*LTS-START*/ +#include "controller_updater.h" +#include "controller_limits.h" +/*LTS-END*/ ///\brief Check the submitted configuration and handle things accordingly. ///\param in The new configuration. @@ -114,6 +118,10 @@ void Controller::checkConfig(JSON::Value & in, JSON::Value & out){ /// Please note that this is NOT secure. At all. Never use this mechanism over a public network! /// A status of `"ACC_MADE"` indicates the account was created successfully and can now be used to login as normal. bool Controller::authorize(JSON::Value & Request, JSON::Value & Response, Socket::Connection & conn){ + #ifdef NOAUTH + Response["authorize"]["status"] = "OK"; + return true; + #endif time_t Time = time(0); tm * TimeInfo = localtime( &Time); std::stringstream Date; @@ -191,6 +199,148 @@ int Controller::handleAPIConnection(Socket::Connection & conn){ if (Request.isMember("streams")){ Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]); } + /*LTS-START*/ + /// + /// \api + /// `"addstream"` requests (LTS-only) take the form of: + /// ~~~~~~~~~~~~~~~{.js} + /// { + /// "streamname": { + /// //Stream configuration - see the "streams" call for details on this format. + /// } + /// /// Optionally, repeat for more streams. + /// } + /// ~~~~~~~~~~~~~~~ + /// These requests will add new streams or update existing streams with the same names, without touching other streams. In other words, this call can be used for incremental updates to the stream list instead of complete updates, like the "streams" call. + /// + if (Request.isMember("addstream")){ + Controller::AddStreams(Request["addstream"], Controller::Storage["streams"]); + } + /// + /// \api + /// `"deletestream"` requests (LTS-only) take the form of: + /// ~~~~~~~~~~~~~~~{.js} + /// { + /// "streamname": {} //any contents in this object are ignored + /// /// Optionally, repeat for more streams. + /// } + /// ~~~~~~~~~~~~~~~ + /// OR + /// ~~~~~~~~~~~~~~~{.js} + /// [ + /// "streamname", + /// /// Optionally, repeat for more streams. + /// ] + /// ~~~~~~~~~~~~~~~ + /// OR + /// ~~~~~~~~~~~~~~~{.js} + /// "streamname" + /// ~~~~~~~~~~~~~~~ + /// These requests will remove the named stream(s), without touching other streams. In other words, this call can be used for incremental updates to the stream list instead of complete updates, like the "streams" call. + /// + if (Request.isMember("deletestream")){ + //if array, delete all elements + //if object, delete all entries + //if string, delete just the one + if (Request["deletestream"].isString()){ + Controller::Storage["streams"].removeMember(Request["deletestream"].asStringRef()); + } + if (Request["deletestream"].isArray()){ + for (JSON::ArrIter it = Request["deletestream"].ArrBegin(); it != Request["deletestream"].ArrEnd(); ++it){ + Controller::Storage["streams"].removeMember(it->asString()); + } + } + if (Request["deletestream"].isObject()){ + for (JSON::ObjIter it = Request["deletestream"].ObjBegin(); it != Request["deletestream"].ObjEnd(); ++it){ + Controller::Storage["streams"].removeMember(it->first); + } + } + } + /// + /// \api + /// `"addprotocol"` requests (LTS-only) take the form of: + /// ~~~~~~~~~~~~~~~{.js} + /// { + /// "connector": "HTTP" //Name of the connector to enable + /// //any required and/or optional settings may be given here as "name": "value" pairs inside this object. + /// } + /// ~~~~~~~~~~~~~~~ + /// OR + /// ~~~~~~~~~~~~~~~{.js} + /// [ + /// { + /// "connector": "HTTP" //Name of the connector to enable + /// //any required and/or optional settings may be given here as "name": "value" pairs inside this object. + /// } + /// /// Optionally, repeat for more protocols. + /// ] + /// ~~~~~~~~~~~~~~~ + /// These requests will add the given protocol configurations, without touching existing configurations. In other words, this call can be used for incremental updates to the protocols list instead of complete updates, like the "config" call. + /// There is no response to this call. + /// + if (Request.isMember("addprotocol")){ + if (Request["addprotocol"].isArray()){ + for (JSON::ArrIter it = Request["addprotocol"].ArrBegin(); it != Request["addprotocol"].ArrEnd(); ++it){ + Controller::Storage["config"]["protocols"].append(*it); + } + } + if (Request["addprotocol"].isObject()){ + Controller::Storage["config"]["protocols"].append(Request["addprotocol"]); + } + Controller::CheckProtocols(Controller::Storage["config"]["protocols"], capabilities); + } + /// + /// \api + /// `"deleteprotocol"` requests (LTS-only) take the form of: + /// ~~~~~~~~~~~~~~~{.js} + /// { + /// "connector": "HTTP" //Name of the connector to enable + /// //any required and/or optional settings may be given here as "name": "value" pairs inside this object. + /// } + /// ~~~~~~~~~~~~~~~ + /// OR + /// ~~~~~~~~~~~~~~~{.js} + /// [ + /// { + /// "connector": "HTTP" //Name of the connector to enable + /// //any required and/or optional settings may be given here as "name": "value" pairs inside this object. + /// } + /// /// Optionally, repeat for more protocols. + /// ] + /// ~~~~~~~~~~~~~~~ + /// These requests will remove the given protocol configurations (exact matches only), without touching other configurations. In other words, this call can be used for incremental updates to the protocols list instead of complete updates, like the "config" call. + /// There is no response to this call. + /// + if (Request.isMember("deleteprotocol")){ + if (Request["deleteprotocol"].isArray() && Request["deleteprotocol"].size()){ + JSON::Value newProtocols; + for (JSON::ArrIter it = Controller::Storage["config"]["protocols"].ArrBegin(); it != Controller::Storage["config"]["protocols"].ArrEnd(); ++it){ + bool add = true; + for (JSON::ArrIter pit = Request["deleteprotocol"].ArrBegin(); pit != Request["deleteprotocol"].ArrEnd(); ++pit){ + if (*it == *pit){ + add = false; + break; + } + } + if (add){ + newProtocols.append(*it); + } + } + Controller::Storage["config"]["protocols"] = newProtocols; + Controller::CheckProtocols(Controller::Storage["config"]["protocols"], capabilities); + } + if (Request["deleteprotocol"].isObject()){ + JSON::Value newProtocols; + for (JSON::ArrIter it = Controller::Storage["config"]["protocols"].ArrBegin(); it != Controller::Storage["config"]["protocols"].ArrEnd(); ++it){ + if (*it != Request["deleteprotocol"]){ + newProtocols.append(*it); + } + } + Controller::Storage["config"]["protocols"] = newProtocols; + Controller::CheckProtocols(Controller::Storage["config"]["protocols"], capabilities); + } + } + /*LTS-END*/ if (Request.isMember("capabilities")){ Controller::checkCapable(capabilities); Response["capabilities"] = capabilities; @@ -248,6 +398,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){ /// ] /// ] /// ~~~~~~~~~~~~~~~ + /// if(Request.isMember("browse")){ if(Request["browse"] == ""){ Request["browse"] = "."; @@ -312,6 +463,28 @@ int Controller::handleAPIConnection(Socket::Connection & conn){ } Response["ui_settings"] = Storage["ui_settings"]; } + /*LTS-START*/ + /// + /// \api + /// LTS builds will always include an `"LTS"` response, set to 1. + /// + Response["LTS"] = 1; + /// + /// \api + /// `"autoupdate"` requests (LTS-only) will cause MistServer to apply a rolling update to itself, and are not responded to. + /// + #ifdef UPDATER + if (Request.isMember("autoupdate")){ + Controller::CheckUpdates(); + } + if (Request.isMember("checkupdate")){ + Controller::updates = Controller::CheckUpdateInfo(); + } + if (Request.isMember("update") || Request.isMember("checkupdate")){ + Response["update"] = Controller::updates; + } + #endif + /*LTS-END*/ //sent current configuration, no matter if it was changed or not Response["config"] = Controller::Storage["config"]; Response["config"]["version"] = PACKAGE_VERSION "/" + Util::Config::libver + "/" RELEASE; @@ -363,6 +536,9 @@ int Controller::handleAPIConnection(Socket::Connection & conn){ Controller::fillTotals(Request["totals"], Response["totals"]); } } + if (Request.isMember("active_streams")){ + Controller::fillActive(Request["active_streams"], Response["active_streams"]); + } Controller::writeConfig(); @@ -370,6 +546,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){ Util::sleep(1000);//sleep a second to prevent bruteforcing logins++; } + Controller::checkServerLimits(); /*LTS*/ }//config mutex lock //send the response, either normally or through JSONP callback. std::string jsonp = ""; diff --git a/src/controller/controller_limits.cpp b/src/controller/controller_limits.cpp new file mode 100644 index 00000000..398ff06f --- /dev/null +++ b/src/controller/controller_limits.cpp @@ -0,0 +1,403 @@ +#include "controller_limits.h" +#include "controller_statistics.h" +#include "controller_storage.h" + +#include +#include +#include +#include + +namespace Controller{ + void checkStreamLimits(std::string streamName, long long currentKbps, long long connectedUsers){ + if( !Storage["streams"].isMember(streamName)){ + return; + } + if( !Storage["streams"][streamName].isMember("limits")){ + return; + } + if( !Storage["streams"][streamName]["limits"]){ + return; + } + + Storage["streams"][streamName].removeMember("hardlimit_active"); + if (Storage["streams"][streamName]["online"].asInt() != 1){ + for (JSON::ArrIter limitIt = Storage["streams"][streamName]["limits"].ArrBegin(); limitIt != Storage["streams"][streamName]["limits"].ArrEnd(); limitIt++){ + if ((*limitIt).isMember("triggered")){ + if ((*limitIt)["type"].asString() == "soft"){ + Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable."); + }else{ + Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable."); + } + (*limitIt).removeMember("triggered"); + } + } + return; + } + + //run over all limits. + for (JSON::ArrIter limitIt = Storage["streams"][streamName]["limits"].ArrBegin(); limitIt != Storage["streams"][streamName]["limits"].ArrEnd(); limitIt++){ + bool triggerLimit = false; + if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){ + triggerLimit = true; + } + if ((*limitIt)["name"].asString() == "kbps_max" && currentKbps >= (*limitIt)["value"].asInt()){ + triggerLimit = true; + } + if (triggerLimit){ + if ((*limitIt)["type"].asString() == "hard"){ + Storage["streams"][streamName]["hardlimit_active"] = true; + } + if ((*limitIt).isMember("triggered")){ + continue; + } + if ((*limitIt)["type"].asString() == "soft"){ + Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered."); + }else{ + Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered."); + } + (*limitIt)["triggered"] = true; + }else{ + if ( !(*limitIt).isMember("triggered")){ + continue; + } + if ((*limitIt)["type"].asString() == "soft"){ + Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset."); + }else{ + Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset."); + } + (*limitIt).removeMember("triggered"); + } + } + } + + void checkServerLimits(){ + + int currentKbps = 0; + int connectedUsers = 0; + std::map strmUsers; + std::map strmBandw; + + /* + if (curConns.size()){ + for (std::map::iterator it = curConns.begin(); it != curConns.end(); it++){ + if (it->second.log.size() < 2){continue;} + std::map::reverse_iterator statRef = it->second.log.rbegin(); + std::map::reverse_iterator prevRef = --(it->second.log.rbegin()); + unsigned int diff = statRef->first - prevRef->first; + strmUsers[it->second.streamName]++; + connectedUsers++; + strmBandw[it->second.streamName] += (((statRef->second.down - prevRef->second.down) + (statRef->second.up - prevRef->second.up)) / diff); + currentKbps += (((statRef->second.down - prevRef->second.down) + (statRef->second.up - prevRef->second.up)) / diff); + } + } + */ + + //check stream limits + if (Storage["streams"].size()){ + for (JSON::ObjIter strmIt = Storage["streams"].ObjBegin(); strmIt != Storage["streams"].ObjEnd(); strmIt++){ + checkStreamLimits(strmIt->first, strmBandw[strmIt->first], strmUsers[strmIt->first]); + } + } + + Storage["config"].removeMember("hardlimit_active"); + if ( !Storage["config"]["limits"].size()){ + return; + } + if ( !Storage["streams"].size()){ + return; + } + + for (JSON::ArrIter limitIt = Storage["config"]["limits"].ArrBegin(); limitIt != Storage["config"]["limits"].ArrEnd(); limitIt++){ + bool triggerLimit = false; + if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){ + triggerLimit = true; + } + if ((*limitIt)["name"].asString() == "kbps_max" && currentKbps >= (*limitIt)["value"].asInt()){ + triggerLimit = true; + } + if (triggerLimit){ + if ((*limitIt)["type"].asString() == "hard"){ + Storage["config"]["hardlimit_active"] = true; + } + if ((*limitIt).isMember("triggered")){ + continue; + } + if ((*limitIt)["type"].asString() == "soft"){ + Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered."); + }else{ + Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered."); + } + (*limitIt)["triggered"] = true; + }else{ + if ( !(*limitIt).isMember("triggered")){ + continue; + } + if ((*limitIt)["type"].asString() == "soft"){ + Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset."); + }else{ + Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset."); + } + (*limitIt).removeMember("triggered"); + } + } + } + + bool onList(std::string ip, std::string list){ + if (list == ""){ + return false; + } + std::string entry; + std::string lowerIpv6;//lower-case + std::string upperIpv6;//full-caps + do{ + entry = list.substr(0,list.find(" "));//make sure we have a single entry + lowerIpv6 = "::ffff:" + entry; + upperIpv6 = "::FFFF:" + entry; + if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){ + return true; + } + long long unsigned int starPos = entry.find("*"); + if (starPos == std::string::npos){ + if (ip == entry){ + return true; + } + }else{ + if (starPos == 0){//beginning of the filter + if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){ + return true; + } + }else{ + if (starPos == entry.size() - 1){//end of the filter + if (ip.find(entry.substr(0, entry.size() - 1)) == 0 ){ + return true; + } + if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0 ){ + return true; + } + if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0 ){ + return true; + } + }else{ + Log("CONF","Invalid list entry detected: " + entry); + } + } + } + list.erase(0, entry.size() + 1); + }while (list != ""); + return false; + } + + std::string hostLookup(std::string ip){ + struct sockaddr_in6 sa; + char hostName[1024]; + char service[20]; + if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){ + return "\n"; + } + sa.sin6_family = AF_INET6; + sa.sin6_port = 0; + sa.sin6_flowinfo = 0; + sa.sin6_scope_id = 0; + int tmpRet = getnameinfo((struct sockaddr*)&sa, sizeof sa, hostName, sizeof hostName, service, sizeof service, NI_NAMEREQD ); + if ( tmpRet == 0){ + return hostName; + } + return ""; + } + + bool isBlacklisted(std::string host, std::string streamName, int timeConnected){ + std::string myHostName = hostLookup(host); + if (myHostName == "\n"){ + return false; + } + std::string myCountryName = getCountry(host); + JSON::ArrIter limitIt; + bool hasWhitelist = false; + bool hostOnWhitelist = false; + if (Storage["streams"].isMember(streamName)){ + if (Storage["streams"][streamName].isMember("limits") && Storage["streams"][streamName]["limits"].size()){ + for (limitIt = Storage["streams"][streamName]["limits"].ArrBegin(); limitIt != Storage["streams"][streamName]["limits"].ArrEnd(); limitIt++){ + if ((*limitIt)["name"].asString() == "host"){ + if ((*limitIt)["value"].asString()[0] == '+'){ + if (!onList(host, (*limitIt)["value"].asString().substr(1))){ + if (myHostName == ""){ + if (timeConnected > Storage["config"]["limit_timeout"].asInt()){ + return true; + } + }else{ + if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName); + } + } + } + } + }else{ + if ((*limitIt)["value"].asString()[0] == '-'){ + if (onList(host, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " blacklisted for stream " + streamName); + } + } + if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName); + } + } + } + } + } + if ((*limitIt)["name"].asString() == "geo"){ + if ((*limitIt)["value"].asString()[0] == '+'){ + if (myCountryName == ""){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName); + } + } + if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName); + } + } + }else{ + if ((*limitIt)["val"].asString()[0] == '-'){ + if (onList(myCountryName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName); + } + } + } + } + } + } + } + } + if (Storage["config"]["limits"].size()){ + for (limitIt = Storage["config"]["limits"].ArrBegin(); limitIt != Storage["config"]["limits"].ArrEnd(); limitIt++){ + if ((*limitIt)["name"].asString() == "host"){ + if ((*limitIt)["value"].asString()[0] == '+'){ + if (!onList(host, (*limitIt)["value"].asString().substr(1))){ + if (myHostName == ""){ + if (timeConnected > Storage["config"]["limit_timeout"].asInt()){ + return true; + } + }else{ + if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName); + } + } + } + } + }else{ + if ((*limitIt)["value"].asString()[0] == '-'){ + if (onList(host, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " blacklisted for stream " + streamName); + } + } + if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName); + } + } + } + } + } + if ((*limitIt)["name"].asString() == "geo"){ + if ((*limitIt)["value"].asString()[0] == '+'){ + if (myCountryName == ""){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName); + } + } + if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName); + } + } + }else{ + if ((*limitIt)["value"].asString()[0] == '-'){ + if (onList(myCountryName, (*limitIt)["val"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName); + } + } + } + } + } + } + } + if (hasWhitelist){ + if (hostOnWhitelist || myHostName == ""){ + return false; + }else{ + return true; + } + } + return false; + } + + std::string getCountry(std::string ip){ + char * code = NULL; + #ifdef GEOIP + GeoIP * geoIP; + geoIP = GeoIP_open(GEOIPV4, GEOIP_STANDARD | GEOIP_CHECK_CACHE); + if (!geoIP){ + std::cerr << "An error occured loading the IPv4 database" << std::endl; + }else{ + code = (char*)GeoIP_country_code_by_addr(geoIP, ip.c_str()); + GeoIP_delete(geoIP); + } + if (!code){ + geoIP = GeoIP_open(GEOIPV6, GEOIP_STANDARD | GEOIP_CHECK_CACHE); + if (!geoIP){ + std::cerr << "An error occured loading the IPv6 database" << std::endl; + }else{ + code = (char*)GeoIP_country_code_by_addr_v6(geoIP, ip.c_str()); + GeoIP_delete(geoIP); + } + } + #endif + if (!code){ + return ""; + } + return code; + } +} diff --git a/src/controller/controller_limits.h b/src/controller/controller_limits.h new file mode 100644 index 00000000..72fde2a0 --- /dev/null +++ b/src/controller/controller_limits.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include + +/*LTS-START*/ +#ifdef GEOIP +#include +#define GEOIPV4 "/usr/share/GeoIP/GeoIP.dat" +#define GEOIPV6 "/usr/share/GeoIP/GeoIPv6.dat" +#endif +/*LTS-END*/ + +namespace Controller{ + void checkStreamLimits(std::string streamName, long long currentKbps, long long connectedUsers); + void checkServerLimits(); + bool isBlacklisted(std::string host, std::string streamName, int timeConnected); + std::string hostLookup(std::string ip); + bool onList(std::string ip, std::string list); + std::string getCountry(std::string ip); +} diff --git a/src/controller/controller_statistics.cpp b/src/controller/controller_statistics.cpp index 8d0a3719..9305fb81 100644 --- a/src/controller/controller_statistics.cpp +++ b/src/controller/controller_statistics.cpp @@ -1,6 +1,11 @@ #include #include #include "controller_statistics.h" +#include "controller_limits.h" + +#ifndef KILL_ON_EXIT +#define KILL_ON_EXIT false +#endif // These are used to store "clients" field requests in a bitfield for speedup. #define STAT_CLI_HOST 1 @@ -23,6 +28,7 @@ std::map Controller::sessions; ///< list of sessions that have statistics data available std::map Controller::connToSession; ///< Map of socket IDs to session info. +bool Controller::killOnExit = KILL_ON_EXIT; tthread::mutex Controller::statsMutex; Controller::sessIndex::sessIndex(std::string dhost, unsigned int dcrc, std::string dstreamName, std::string dconnector){ @@ -79,6 +85,10 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{ return !(*this < b); } +/// Forces a disconnect to all users. +void Controller::killStatistics(char * data, size_t len, unsigned int id){ + (*(data - 1)) = 128;//Send disconnect message; +} /// This function runs as a thread and roughly once per second retrieves /// statistics from all connected clients, as well as wipes @@ -98,10 +108,19 @@ void Controller::SharedMemStats(void * config){ it->second.wipeOld(cutOffPoint); } } + Controller::checkServerLimits(); /*LTS*/ } Util::sleep(1000); } DEBUG_MSG(DLVL_HIGH, "Stopping stats thread"); + if (Controller::killOnExit){ + DEBUG_MSG(DLVL_WARN, "Killing all connected clients to force full shutdown"); + unsigned int c = 0;//to prevent eternal loops + do{ + statServer.parseEach(killStatistics); + Util::wait(250); + }while(statServer.amount && c++ < 10); + } } /// Updates the given active connection with new stats data. @@ -416,6 +435,11 @@ void Controller::parseStatistics(char * data, size_t len, unsigned int id){ sessions[idx].finish(id); connToSession.erase(id); } + /*LTS-START*/ + //if (counter < 125 && Controller::isBlacklisted(tmpEx.host(), ID, tmpEx.time())){ + // (*(data - 1)) = 128;//Send disconnect message; + //} + /*LTS-END*/ } /// Returns true if this stream has at least one connected client. @@ -554,6 +578,36 @@ void Controller::fillClients(JSON::Value & req, JSON::Value & rep){ //all done! return is by reference, so no need to return anything here. } +/// This takes a "active_streams" request, and fills in the response data. +/// +/// \api +/// `"active_streams"` requests are always empty (passed data is ignored), and are responded to as: +/// ~~~~~~~~~~~~~~~{.js} +/// [ +/// //Array of stream names +/// "streamA", +/// "streamB", +/// "streamC" +/// ] +/// ~~~~~~~~~~~~~~~ +/// All streams that any statistics data is available for are listed, and only those streams. +void Controller::fillActive(JSON::Value & req, JSON::Value & rep){ + //collect the data first + std::set streams; + //check all sessions + if (sessions.size()){ + for (std::map::iterator it = sessions.begin(); it != sessions.end(); it++){ + streams.insert(it->first.streamName); + } + } + //Good, now output what we found... + rep.null(); + for (std::set::iterator it = streams.begin(); it != streams.end(); it++){ + rep.append(*it); + } + //all done! return is by reference, so no need to return anything here. +} + class totalsData { public: totalsData(){ diff --git a/src/controller/controller_statistics.h b/src/controller/controller_statistics.h index cebf1163..0880de7e 100644 --- a/src/controller/controller_statistics.h +++ b/src/controller/controller_statistics.h @@ -11,6 +11,9 @@ namespace Controller { + + extern bool killOnExit; + struct statLog { long time; long lastSecond; @@ -80,7 +83,9 @@ namespace Controller { extern std::map connToSession; extern tthread::mutex statsMutex; 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); void fillTotals(JSON::Value & req, JSON::Value & rep); void SharedMemStats(void * config); bool hasViewers(std::string streamName); diff --git a/src/controller/controller_streams.cpp b/src/controller/controller_streams.cpp index 3ea14fc2..936719f5 100644 --- a/src/controller/controller_streams.cpp +++ b/src/controller/controller_streams.cpp @@ -9,6 +9,7 @@ #include "controller_capabilities.h" #include "controller_storage.h" #include "controller_statistics.h" +#include "controller_limits.h" /*LTS*/ #include #include @@ -73,6 +74,7 @@ namespace Controller { trackIt->second.removeMember("keys"); trackIt->second.removeMember("keysizes"); trackIt->second.removeMember("parts"); + trackIt->second.removeMember("ivecs");/*LTS*/ } } } @@ -82,7 +84,7 @@ namespace Controller { //vod-style stream data.removeMember("error"); struct stat fileinfo; - if (stat(URL.c_str(), &fileinfo) != 0 || S_ISDIR(fileinfo.st_mode)){ + if (stat(URL.c_str(), &fileinfo) != 0){ data["error"] = "Stream offline: Not found: " + URL; if (data["error"].asStringRef() != prevState){ Log("BUFF", "Warning for VoD stream " + name + "! File not found: " + URL); @@ -121,6 +123,9 @@ namespace Controller { DEBUG_MSG(DLVL_INSANE, "Invalid metadata (no tracks object) for stream %s - triggering reload", name.c_str()); getMeta = true; } + if (*(URL.rbegin()) == '/'){ + getMeta = false; + } if (getMeta){ // if the file isn't dtsc and there's no dtsh file, run getStream on it // this guarantees that if the stream is playable, it now has a valid header. @@ -198,6 +203,7 @@ namespace Controller { }else{ data["online"] = 1; } + checkServerLimits(); /*LTS*/ return; } /// \todo Implement ffmpeg pulling again? @@ -231,6 +237,7 @@ namespace Controller { } jit->second["online"] = 0; } + checkServerLimits(); /*LTS*/ }else{ // assume all is fine jit->second.removeMember("error"); @@ -242,7 +249,7 @@ namespace Controller { jit->second["error"] = "No (valid) source connected "; }else{ // for live streams, keep track of activity - if (jit->second["meta"].isMember("live")){ + if (jit->second.isMember("meta") && jit->second["meta"].isMember("live")){ static std::map checker; //check H264 tracks for optimality if (jit->second.isMember("meta") && jit->second["meta"].isMember("tracks")){ @@ -251,7 +258,11 @@ namespace Controller { checker[jit->first].lastms = trIt->second["lastms"].asInt(); checker[jit->first].last_active = currTime; } - + /*LTS-START*/ + if (trIt->second["firstms"].asInt() > Storage["streams"][jit->first]["cut"].asInt()){ + Storage["streams"][jit->first].removeMember("cut"); + } + /*LTS-END*/ } } // mark stream as offline if no activity for 5 seconds @@ -289,14 +300,8 @@ namespace Controller { Log("STRM", std::string("Updated stream ") + jit->first); } }else{ + out[jit->first] = jit->second; out[jit->first]["name"] = jit->first; - out[jit->first]["source"] = jit->second["source"]; - if (jit->second.isMember("DVR")){ - out[jit->first]["DVR"] = jit->second["DVR"].asInt(); - } - if (jit->second.isMember("cut")){ - out[jit->first]["cut"] = jit->second["cut"].asInt(); - } Log("STRM", std::string("New stream ") + jit->first); } } diff --git a/src/controller/controller_updater.cpp b/src/controller/controller_updater.cpp new file mode 100644 index 00000000..b2a46491 --- /dev/null +++ b/src/controller/controller_updater.cpp @@ -0,0 +1,253 @@ +/// \file controller_updater.cpp +/// Contains all code for the controller updater. + +#include //for files +#include //for stdio +#include //for unlink +#include //for chmod +#include //for srand, rand +#include //for time +#include //for raise +#include +#include +#include +#include +#include +#include "controller_storage.h" +#include "controller_connectors.h" +#include "controller_updater.h" + +namespace Controller { + bool restarting = false; + JSON::Value updates; + std::string uniqId; + + 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; + } //readFile + + 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; + } //writeFile + + /// \api + /// `"update"` and `"checkupdate"` requests (LTS-only) are responded to as: + /// ~~~~~~~~~~~~~~~{.js} + /// { + /// "error": "Something went wrong", // 'Optional' + /// "release": "LTS64_99", + /// "version": "1.2 / 6.0.0", + /// "date": "January 5th, 2014", + /// "uptodate": 0, + /// "needs_update": ["MistBuffer", "MistController"], //Controller is guaranteed to be last + /// "MistController": "abcdef1234567890", //md5 sum of latest version + /// //... all other MD5 sums follow + /// } + /// ~~~~~~~~~~~~~~~ + /// Note that `"update"` will only list known information, while `"checkupdate"` triggers an information refresh from the update server. + JSON::Value CheckUpdateInfo(){ + JSON::Value ret; + + if (uniqId == ""){ + srand(time(NULL)); + do{ + char meh = 64 + rand() % 62; + uniqId += meh; + }while(uniqId.size() < 16); + } + + //initialize connection + HTTP::Parser http; + JSON::Value updrInfo; + Socket::Connection updrConn("releases.mistserver.org", 80, true); + if ( !updrConn){ + Log("UPDR", "Could not connect to releases.mistserver.org to get update information."); + ret["error"] = "Could not connect to releases.mistserver.org to get update information."; + return ret; + } + + //retrieve update information + http.url = "/getsums.php?verinfo=1&rel=" RELEASE "&pass=" SHARED_SECRET "&uniqId=" + uniqId; + http.method = "GET"; + http.SetHeader("Host", "releases.mistserver.org"); + http.SetHeader("X-Version", PACKAGE_VERSION "/" + Util::Config::libver + "/" RELEASE); + updrConn.SendNow(http.BuildRequest()); + http.Clean(); + unsigned int startTime = Util::epoch(); + while ((Util::epoch() - startTime < 10) && (updrConn || updrConn.Received().size())){ + if (updrConn.spool() || updrConn.Received().size()){ + if ( *(updrConn.Received().get().rbegin()) != '\n'){ + std::string tmp = updrConn.Received().get(); + updrConn.Received().get().clear(); + if (updrConn.Received().size()){ + updrConn.Received().get().insert(0, tmp); + }else{ + updrConn.Received().append(tmp); + } + continue; + } + if (http.Read(updrConn.Received().get())){ + updrInfo = JSON::fromString(http.body); + break; //break out of while loop + } + } + } + updrConn.close(); + + if (updrInfo){ + if (updrInfo.isMember("error")){ + Log("UPDR", updrInfo["error"].asStringRef()); + ret["error"] = updrInfo["error"]; + ret["uptodate"] = 1; + return ret; + } + ret["release"] = RELEASE; + if (updrInfo.isMember("version")){ + ret["version"] = updrInfo["version"]; + } + if (updrInfo.isMember("date")){ + ret["date"] = updrInfo["date"]; + } + ret["uptodate"] = 1; + ret["needs_update"].null(); + + // check if everything is up to date or not + for (JSON::ObjIter it = updrInfo.ObjBegin(); it != updrInfo.ObjEnd(); it++){ + if (it->first.substr(0, 4) != "Mist"){ + continue; + } + ret[it->first] = it->second; + if (it->second.asString() != Secure::md5(readFile(Util::getMyPath() + it->first))){ + ret["uptodate"] = 0; + if (it->first.substr(0, 14) == "MistController"){ + ret["needs_update"].append(it->first); + }else{ + ret["needs_update"].prepend(it->first); + } + } + } + }else{ + Log("UPDR", "Could not retrieve update information from releases server."); + ret["error"] = "Could not retrieve update information from releases server."; + } + return ret; + } + + /// Calls CheckUpdateInfo(), uses the resulting JSON::Value to download any needed updates. + /// Will shut down the server if the JSON::Value contained a "shutdown" member. + void CheckUpdates(){ + JSON::Value updrInfo = CheckUpdateInfo(); + if (updrInfo.isMember("error")){ + Log("UPDR", "Error retrieving update information: " + updrInfo["error"].asString()); + return; + } + + if (updrInfo.isMember("shutdown")){ + Log("DDVT", "Shutting down: " + updrInfo["shutdown"].asString()); + restarting = false; + raise(SIGINT); //trigger shutdown + return; + } + + if (updrInfo["uptodate"]){ + //nothing to do + return; + } + + //initialize connection + Socket::Connection updrConn("releases.mistserver.org", 80, true); + if ( !updrConn){ + Log("UPDR", "Could not connect to releases.mistserver.org."); + return; + } + + //loop through the available components, update them + for (JSON::ArrIter it = updrInfo["needs_update"].ArrBegin(); it != updrInfo["needs_update"].ArrEnd(); it++){ + updateComponent(it->asStringRef(), updrInfo[it->asStringRef()].asStringRef(), updrConn); + } + updrConn.close(); + } //CheckUpdates + + /// Attempts to download an update for the listed component. + /// \param component Filename of the component being checked. + /// \param md5sum The MD5 sum of the latest version of this file. + /// \param updrConn An connection to releases.mistserver.org to (re)use. Will be (re)opened if closed. + void updateComponent(const std::string & component, const std::string & md5sum, Socket::Connection & updrConn){ + Log("UPDR", "Downloading update for " + component); + std::string new_file; + HTTP::Parser http; + http.url = "/getfile.php?rel=" RELEASE "&pass=" SHARED_SECRET "&file=" + component; + http.method = "GET"; + http.SetHeader("Host", "releases.mistserver.org"); + if ( !updrConn){ + updrConn = Socket::Connection("releases.mistserver.org", 80, true); + if ( !updrConn){ + Log("UPDR", "Could not connect to releases.mistserver.org for file download."); + return; + } + } + http.SendRequest(updrConn); + http.Clean(); + unsigned int startTime = Util::epoch(); + while ((Util::epoch() - startTime < 90) && (updrConn || updrConn.Received().size())){ + if (updrConn.spool() || updrConn.Received().size()){ + if ( *(updrConn.Received().get().rbegin()) != '\n'){ + std::string tmp = updrConn.Received().get(); + updrConn.Received().get().clear(); + if (updrConn.Received().size()){ + updrConn.Received().get().insert(0, tmp); + }else{ + updrConn.Received().append(tmp); + } + } + if (http.Read(updrConn.Received().get())){ + new_file = http.body; + break; //break out of while loop + } + } + } + http.Clean(); + if (new_file == ""){ + Log("UPDR", "Could not retrieve new version of " + component + " - retrying next time."); + return; + } + if (Secure::md5(new_file) != md5sum){ + Log("UPDR", "Checksum "+Secure::md5(new_file)+" of " + component + " does not match "+md5sum+" - retrying next time."); + return; + } + if (writeFile(Util::getMyPath() + component, new_file)){ + Controller::UpdateProtocol(component); + if (component == "MistController"){ + restarting = true; + raise(SIGINT); //trigger restart + } + Log("UPDR", "New version of " + component + " installed."); + }else{ + Log("UPDR", component + " could not be updated! (No write access to file?)"); + } + } + + +} //Controller namespace diff --git a/src/controller/controller_updater.h b/src/controller/controller_updater.h new file mode 100644 index 00000000..2fdb305f --- /dev/null +++ b/src/controller/controller_updater.h @@ -0,0 +1,21 @@ +/// \file controller_updater.cpp +/// Contains all code for the controller updater. + +#include + +#ifndef SHARED_SECRET +#define SHARED_SECRET "empty" +#endif + +namespace Controller { + extern bool restarting;///< Signals if the controller is shutting down (false) or restarting (true). + extern JSON::Value updates; + extern std::string uniqId; + + std::string readFile(std::string filename); + bool writeFile(std::string filename, std::string & contents); + JSON::Value CheckUpdateInfo(); + void CheckUpdates(); + void updateComponent(const std::string & component, const std::string & md5sum, Socket::Connection & updrConn); + +} //Controller namespace diff --git a/src/controller/controller_uplink.cpp b/src/controller/controller_uplink.cpp new file mode 100644 index 00000000..74690e23 --- /dev/null +++ b/src/controller/controller_uplink.cpp @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include +#include "controller_uplink.h" +#include "controller_storage.h" +#include "controller_streams.h" +#include "controller_connectors.h" +#include "controller_capabilities.h" +#include "controller_statistics.h" +#include "controller_updater.h" +#include "controller_limits.h" +#include "controller_api.h" + +void Controller::uplinkConnection(void * np) { + std::string uplink_name = Controller::conf.getString("uplink-name"); + std::string uplink_pass = Controller::conf.getString("uplink-pass"); + std::string uplink_addr = Controller::conf.getString("uplink"); + std::string uplink_host = ""; + std::string uplink_chal = ""; + int uplink_port = 0; + if (uplink_addr.size() > 0) { + size_t colon = uplink_addr.find(':'); + if (colon != std::string::npos && colon != 0 && colon != uplink_addr.size()) { + uplink_host = uplink_addr.substr(0, colon); + uplink_port = atoi(uplink_addr.substr(colon + 1, std::string::npos).c_str()); + Controller::Log("CONF", "Connection to uplink enabled on host " + uplink_host + " and port " + uplink_addr.substr(colon + 1, std::string::npos)); + } + } + //cancel the whole thread if no uplink is set + if (!uplink_port) { + return; + } + + if (uniqId == ""){ + srand(time(NULL)); + do{ + char meh = 64 + rand() % 62; + uniqId += meh; + }while(uniqId.size() < 16); + } + + unsigned long long lastSend = Util::epoch() - 5; + Socket::Connection uplink; + while (Controller::conf.is_active) { + if (!uplink) { + INFO_MSG("Connecting to uplink at %s:%u", uplink_host.c_str(), uplink_port); + uplink = Socket::Connection(uplink_host, uplink_port, true); + } + if (uplink) { + if (uplink.spool()) { + if (uplink.Received().available(9)) { + std::string data = uplink.Received().copy(8); + if (data.substr(0, 4) != "DTSC") { + uplink.Received().clear(); + continue; + } + unsigned int size = ntohl(*(const unsigned int *)(data.data() + 4)); + if (uplink.Received().available(8 + size)) { + std::string packet = uplink.Received().remove(8 + size); + DTSC::Scan inScan = DTSC::Packet(packet.data(), packet.size()).getScan(); + if (!inScan){continue;} + JSON::Value curVal; + //Parse config and streams from the request. + if (inScan.hasMember("authorize") && inScan.getMember("authorize").hasMember("challenge")){ + uplink_chal = inScan.getMember("authorize").getMember("challenge").asString(); + } + if (inScan.hasMember("config")) { + curVal = inScan.getMember("config").asJSON(); + Controller::checkConfig(curVal, Controller::Storage["config"]); + Controller::CheckProtocols(Controller::Storage["config"]["protocols"], capabilities); + } + if (inScan.hasMember("streams")) { + curVal = inScan.getMember("streams").asJSON(); + Controller::CheckStreams(curVal, Controller::Storage["streams"]); + } + if (inScan.hasMember("addstream")) { + curVal = inScan.getMember("addstream").asJSON(); + Controller::AddStreams(curVal, Controller::Storage["streams"]); + Controller::CheckAllStreams(Controller::Storage["streams"]); + } + if (inScan.hasMember("deletestream")) { + curVal = inScan.getMember("deletestream").asJSON(); + //if array, delete all elements + //if object, delete all entries + //if string, delete just the one + if (curVal.isString()) { + Controller::Storage["streams"].removeMember(curVal.asStringRef()); + } + if (curVal.isArray()) { + for (JSON::ArrIter it = curVal.ArrBegin(); it != curVal.ArrEnd(); ++it) { + Controller::Storage["streams"].removeMember(it->asString()); + } + } + if (curVal.isObject()) { + for (JSON::ObjIter it = curVal.ObjBegin(); it != curVal.ObjEnd(); ++it) { + Controller::Storage["streams"].removeMember(it->first); + } + } + Controller::CheckAllStreams(Controller::Storage["streams"]); + } + } + } + } + if (Util::epoch() - lastSend >= 2) { + JSON::Value data; + data["tracks"].null();//make sure the data is encoded as DTSC + if (uplink_chal.size()){ + data["authorize"]["username"] = uplink_name; + data["authorize"]["password"] = Secure::md5( Secure::md5(uplink_pass) + uplink_chal); + } + JSON::Value totalsRequest; + Controller::fillClients(totalsRequest, data["clients"]); + totalsRequest["start"] = (long long)lastSend; + Controller::fillTotals(totalsRequest, data["totals"]); + data["streams"] = Controller::Storage["streams"]; + for (JSON::ObjIter it = data["streams"].ObjBegin(); it != data["streams"].ObjEnd(); it++){ + it->second.removeMember("meta"); + it->second.removeMember("l_meta"); + it->second.removeMember("name"); + } + data["config"] = Controller::Storage["config"]; + data["config"]["uniq"] = uniqId; + data["config"]["version"] = PACKAGE_VERSION "/" + Util::Config::libver + "/" RELEASE; + Controller::checkCapable(capabilities); + data["capabilities"] = capabilities; + data["capabilities"].removeMember("connectors"); + data.sendTo(uplink); + lastSend = Util::epoch(); + } + } else { + Controller::Log("UPLK", "Could not connect to uplink."); + } + Util::wait(2000);//wait for 2.5 seconds + } +} diff --git a/src/controller/controller_uplink.h b/src/controller/controller_uplink.h new file mode 100644 index 00000000..40337b1a --- /dev/null +++ b/src/controller/controller_uplink.h @@ -0,0 +1,3 @@ +namespace Controller { + void uplinkConnection(void * np); +} diff --git a/src/input/input_av.cpp b/src/input/input_av.cpp new file mode 100644 index 00000000..20ba219b --- /dev/null +++ b/src/input/input_av.cpp @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "input_av.h" + +namespace Mist { + inputAV::inputAV(Util::Config * cfg) : Input(cfg) { + pFormatCtx = 0; + capa["name"] = "AV"; + capa["decs"] = "Enables generic avformat/avcodec based input"; + capa["source_match"] = "/*"; + capa["priority"] = 1ll; + capa["codecs"][0u][0u].null(); + capa["codecs"][0u][1u].null(); + capa["codecs"][0u][2u].null(); + av_register_all(); + AVCodec * cInfo = 0; + while ((cInfo = av_codec_next(cInfo)) != 0){ + if (cInfo->type == AVMEDIA_TYPE_VIDEO){ + capa["codecs"][0u][0u].append(cInfo->name); + } + if (cInfo->type == AVMEDIA_TYPE_AUDIO){ + capa["codecs"][0u][1u].append(cInfo->name); + } + if (cInfo->type == AVMEDIA_TYPE_SUBTITLE){ + capa["codecs"][0u][3u].append(cInfo->name); + } + } + } + + inputAV::~inputAV(){ + if (pFormatCtx){ + avformat_close_input(&pFormatCtx); + } + } + + bool inputAV::setup() { + if (config->getString("input") == "-") { + std::cerr << "Input from stdin not yet supported" << std::endl; + return false; + } + 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; + } + } + + //make sure all av inputs are registered properly, just in case + //setup() already does this, but under windows it doesn't remember that it has. + //Very sad, that. We may need to get windows some medication for it. + av_register_all(); + + //close any already open files + if (pFormatCtx){ + avformat_close_input(&pFormatCtx); + pFormatCtx = 0; + } + + //Open video file + int ret = avformat_open_input(&pFormatCtx, config->getString("input").c_str(), NULL, NULL); + if(ret != 0){ + char errstr[300]; + av_strerror(ret, errstr, 300); + DEBUG_MSG(DLVL_FAIL, "Could not open file: %s", errstr); + return false; // Couldn't open file + } + + //Retrieve stream information + ret = avformat_find_stream_info(pFormatCtx, NULL); + if(ret < 0){ + char errstr[300]; + av_strerror(ret, errstr, 300); + DEBUG_MSG(DLVL_FAIL, "Could not find stream info: %s", errstr); + return false; + } + return true; + } + + bool inputAV::readHeader() { + //See whether a separate header file exists. + DTSC::File tmp(config->getString("input") + ".dtsh"); + if (tmp){ + myMeta = tmp.getMeta(); + return true; + } + + myMeta.tracks.clear(); + myMeta.live = false; + myMeta.vod = true; + for(unsigned int i=0; i < pFormatCtx->nb_streams; ){ + AVStream * strm = pFormatCtx->streams[i++]; + myMeta.tracks[i].trackID = i; + switch (strm->codec->codec_id){ + case AV_CODEC_ID_HEVC: + myMeta.tracks[i].codec = "HEVC"; + break; + case AV_CODEC_ID_H264: + myMeta.tracks[i].codec = "H264"; + break; + case AV_CODEC_ID_THEORA: + myMeta.tracks[i].codec = "theora"; + break; + case AV_CODEC_ID_VORBIS: + myMeta.tracks[i].codec = "vorbis"; + break; + case AV_CODEC_ID_OPUS: + myMeta.tracks[i].codec = "opus"; + break; + case AV_CODEC_ID_AAC: + myMeta.tracks[i].codec = "AAC"; + break; + case AV_CODEC_ID_MP3: + myMeta.tracks[i].codec = "MP3"; + break; + case AV_CODEC_ID_AC3: + case AV_CODEC_ID_EAC3: + myMeta.tracks[i].codec = "AC3"; + break; + default: + const AVCodecDescriptor *desc = av_codec_get_codec_descriptor(strm->codec); + if (desc && desc->name){ + myMeta.tracks[i].codec = desc->name; + }else{ + myMeta.tracks[i].codec = "?"; + } + break; + } + if (strm->codec->extradata_size){ + myMeta.tracks[i].init = std::string((char*)strm->codec->extradata, strm->codec->extradata_size); + } + if(strm->codec->codec_type == AVMEDIA_TYPE_VIDEO){ + myMeta.tracks[i].type = "video"; + if (strm->avg_frame_rate.den && strm->avg_frame_rate.num){ + myMeta.tracks[i].fpks = (strm->avg_frame_rate.num * 1000) / strm->avg_frame_rate.den; + }else{ + myMeta.tracks[i].fpks = 0; + } + myMeta.tracks[i].width = strm->codec->width; + myMeta.tracks[i].height = strm->codec->height; + } + if(strm->codec->codec_type == AVMEDIA_TYPE_AUDIO){ + myMeta.tracks[i].type = "audio"; + myMeta.tracks[i].rate = strm->codec->sample_rate; + switch (strm->codec->sample_fmt){ + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + myMeta.tracks[i].size = 8; + break; + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + myMeta.tracks[i].size = 16; + break; + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + case AV_SAMPLE_FMT_FLT: + case AV_SAMPLE_FMT_FLTP: + myMeta.tracks[i].size = 32; + break; + case AV_SAMPLE_FMT_DBL: + case AV_SAMPLE_FMT_DBLP: + myMeta.tracks[i].size = 64; + break; + default: + myMeta.tracks[i].size = 0; + break; + } + myMeta.tracks[i].channels = strm->codec->channels; + } + } + + AVPacket packet; + while(av_read_frame(pFormatCtx, &packet)>=0){ + AVStream * strm = pFormatCtx->streams[packet.stream_index]; + JSON::Value pkt; + pkt["trackid"] = (long long)packet.stream_index + 1; + pkt["data"] = std::string((char*)packet.data, packet.size); + pkt["time"] = (long long)(packet.dts * 1000 * strm->time_base.num / strm->time_base.den); + if (pkt["time"].asInt() < 0){ + pkt["time"] = 0ll; + } + if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){ + pkt["keyframe"] = 1ll; + pkt["bpos"] = (long long)packet.pos; + } + if (packet.pts != AV_NOPTS_VALUE && packet.pts != packet.dts){ + pkt["offset"] = (long long)((packet.pts - packet.dts) * 1000 * strm->time_base.num / strm->time_base.den); + } + myMeta.update(pkt); + av_free_packet(&packet); + } + myMeta.live = false; + myMeta.vod = true; + + //store dtsc-style header file for faster processing, later + std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str()); + oFile << myMeta.toJSON().toNetPacked(); + oFile.close(); + + seek(0); + return true; + } + + void inputAV::getNext(bool smart) { + 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); + continue; + } + AVStream * strm = pFormatCtx->streams[packet.stream_index]; + JSON::Value pkt; + pkt["trackid"] = (long long)packet.stream_index + 1; + pkt["data"] = std::string((char*)packet.data, packet.size); + pkt["time"] = (long long)(packet.dts * 1000 * strm->time_base.num / strm->time_base.den); + if (pkt["time"].asInt() < 0){ + pkt["time"] = 0ll; + } + if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){ + pkt["keyframe"] = 1ll; + pkt["bpos"] = (long long)packet.pos; + } + if (packet.pts != AV_NOPTS_VALUE && packet.pts != packet.dts){ + pkt["offset"] = (long long)((packet.pts - packet.dts) * 1000 * strm->time_base.num / strm->time_base.den); + } + pkt.netPrepare(); + lastPack.reInit(pkt.toNetPacked().data(), pkt.toNetPacked().size()); + av_free_packet(&packet); + return;//success! + } + lastPack.null(); + setup(); + //failure :-( + DEBUG_MSG(DLVL_FAIL, "getNext failed"); + } + + void inputAV::seek(int seekTime) { + int stream_index = av_find_default_stream_index(pFormatCtx); + //Convert ts to frame + unsigned long long reseekTime = av_rescale(seekTime, pFormatCtx->streams[stream_index]->time_base.den, pFormatCtx->streams[stream_index]->time_base.num); + reseekTime /= 1000; + unsigned long long seekStreamDuration = pFormatCtx->streams[stream_index]->duration; + int flags = AVSEEK_FLAG_BACKWARD; + if (reseekTime > 0 && reseekTime < seekStreamDuration){ + flags |= AVSEEK_FLAG_ANY; // H.264 I frames don't always register as "key frames" in FFmpeg + } + int ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, flags); + if (ret < 0){ + ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, AVSEEK_FLAG_ANY); + } + } + + void inputAV::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); + } else { + trackSpec = ""; + } + } + //inFile.selectTracks(selectedTracks); + } +} + diff --git a/src/input/input_av.h b/src/input/input_av.h new file mode 100644 index 00000000..9cc2892e --- /dev/null +++ b/src/input/input_av.h @@ -0,0 +1,32 @@ +#ifndef INT64_MIN +#define INT64_MIN (-(9223372036854775807 ## LL)-1) +#endif + +#ifndef INT64_MAX +#define INT64_MAX ((9223372036854775807 ## LL)) +#endif + +#include "input.h" +extern "C" { + #include + #include +} + +namespace Mist { + class inputAV : public Input { + public: + inputAV(Util::Config * cfg); + ~inputAV(); + protected: + //Private Functions + bool setup(); + bool readHeader(); + void getNext(bool smart = true); + void seek(int seekTime); + void trackSelect(std::string trackSpec); + private: + AVFormatContext *pFormatCtx; + }; +} + +typedef Mist::inputAV mistIn; diff --git a/src/input/input_buffer.cpp b/src/input/input_buffer.cpp index 72ae71d9..d33943b5 100644 --- a/src/input/input_buffer.cpp +++ b/src/input/input_buffer.cpp @@ -31,6 +31,44 @@ namespace Mist { capa["optional"]["DVR"]["option"] = "--buffer"; capa["optional"]["DVR"]["type"] = "uint"; capa["optional"]["DVR"]["default"] = 50000LL; + /*LTS-start*/ + option.null(); + option["arg"] = "string"; + option["long"] = "record"; + option["short"] = "r"; + option["help"] = "Record the stream to a file"; + option["value"].append(""); + config->addOption("record", option); + capa["optional"]["record"]["name"] = "Record to file"; + capa["optional"]["record"]["help"] = "Filename to record the stream to."; + capa["optional"]["record"]["option"] = "--record"; + capa["optional"]["record"]["type"] = "str"; + capa["optional"]["record"]["default"] = ""; + option.null(); + option["arg"] = "integer"; + option["long"] = "cut"; + option["short"] = "c"; + option["help"] = "Any timestamps before this will be cut from the live buffer"; + option["value"].append(0LL); + config->addOption("cut", option); + capa["optional"]["cut"]["name"] = "Cut time (ms)"; + capa["optional"]["cut"]["help"] = "Any timestamps before this will be cut from the live buffer."; + capa["optional"]["cut"]["option"] = "--cut"; + capa["optional"]["cut"]["type"] = "uint"; + capa["optional"]["cut"]["default"] = 0LL; + option.null(); + option["arg"] = "integer"; + option["long"] = "segment-size"; + option["short"] = "S"; + option["help"] = "Target time duration in milliseconds for segments"; + option["value"].append(5000LL); + config->addOption("segmentsize", option); + capa["optional"]["segmentsize"]["name"] = "Segment size (ms)"; + capa["optional"]["segmentsize"]["help"] = "Target time duration in milliseconds for segments."; + capa["optional"]["segmentsize"]["option"] = "--segment-size"; + capa["optional"]["segmentsize"]["type"] = "uint"; + capa["optional"]["segmentsize"]["default"] = 5000LL; + /*LTS-end*/ capa["source_match"] = "push://*"; capa["priority"] = 9ll; capa["desc"] = "Provides buffered live input"; @@ -41,6 +79,7 @@ namespace Mist { singleton = this; bufferTime = 0; cutTime = 0; + segmentSize = 0; } inputBuffer::~inputBuffer() { @@ -48,7 +87,29 @@ namespace Mist { 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++) { - while (removeKey(it->first)) {} + std::map & locations = bufferLocations[it->first]; + + //First detect all entries on metaPage + for (int i = 0; i < 8192; i += 8) { + int * tmpOffset = (int *)(metaPages[it->first].mapped + i); + if (tmpOffset[0] == 0 && tmpOffset[1] == 0) { + continue; + } + unsigned long keyNum = ntohl(tmpOffset[0]); + + //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 = ntohl(tmpOffset[1]); + } + 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; + } } } } @@ -92,8 +153,59 @@ namespace Mist { DEBUG_MSG(DLVL_HIGH, "Erasing key %d:%lu", tid, myMeta.tracks[tid].keys[0].getNumber()); //remove all parts of this key for (int i = 0; i < myMeta.tracks[tid].keys[0].getParts(); i++) { + /*LTS-START*/ + if (recFile.is_open()) { + if (!recMeta.tracks.count(tid)) { + recMeta.tracks[tid] = myMeta.tracks[tid]; + recMeta.tracks[tid].reset(); + } + } + /*LTS-END*/ myMeta.tracks[tid].parts.pop_front(); } + ///\todo Fix recording + /*LTS-START + ///\todo Maybe optimize this by keeping track of the byte positions + if (recFile.good()){ + long long unsigned int firstms = myMeta.tracks[tid].keys[0].getTime(); + long long unsigned int lastms = myMeta.tracks[tid].lastms; + if (myMeta.tracks[tid].keys.size() > 1){ + lastms = myMeta.tracks[tid].keys[1].getTime(); + } + DEBUG_MSG(DLVL_DEVEL, "Recording track %d from %llums to %llums", tid, firstms, lastms); + long long unsigned int bpos = 0; + DTSC::Packet recPack; + int pageLen = dataPages[tid][bufferLocations[tid].begin()->first].len; + char * pageMapped = dataPages[tid][bufferLocations[tid].begin()->first].mapped; + while( bpos < (unsigned long long)pageLen) { + int tmpSize = ((int)pageMapped[bpos + 4] << 24) | ((int)pageMapped[bpos + 5] << 16) | ((int)pageMapped[bpos + 6] << 8) | (int)pageMapped[bpos + 7]; + tmpSize += 8; + recPack.reInit(pageMapped + bpos, tmpSize, true); + if (tmpSize != recPack.getDataLen()){ + DEBUG_MSG(DLVL_DEVEL, "Something went wrong while trying to record a packet @ %llu, %d != %d", bpos, tmpSize, recPack.getDataLen()); + break; + } + if (recPack.getTime() >= lastms){/// \todo getTime never reaches >= lastms, so probably the recording bug has something to do with this + DEBUG_MSG(DLVL_HIGH, "Stopping record, %llu >= %llu", recPack.getTime(), lastms); + break; + } + if (recPack.getTime() >= firstms){ + //Actually record to file here + JSON::Value recJSON = recPack.toJSON(); + recJSON["bpos"] = recBpos; + recFile << recJSON.toNetPacked(); + recFile.flush(); + recBpos = recFile.tellp(); + recMeta.update(recJSON); + } + bpos += recPack.getDataLen(); + } + recFile.flush(); + std::ofstream tmp(std::string(recName + ".dtsh").c_str()); + tmp << recMeta.toJSON().toNetPacked(); + tmp.close(); + } + LTS-END*/ //remove the key itself myMeta.tracks[tid].keys.pop_front(); myMeta.tracks[tid].keySizes.pop_front(); @@ -242,6 +354,12 @@ namespace Mist { } void inputBuffer::userCallback(char * data, size_t len, unsigned int id) { + /*LTS-START*/ + //Reload the configuration to make sure we stay up to date with changes through the api + if (Util::epoch() - lastReTime > 4) { + setup(); + } + /*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 @@ -331,6 +449,18 @@ namespace Mist { std::string trackIdentifier = trackMeta.tracks.find(value)->second.getIdentifier(); DEBUG_MSG(DLVL_HIGH, "Attempting colision detection for track %s", trackIdentifier.c_str()); + /*LTS-START*/ + //Get the identifier for the track, and attempt colission detection. + int collidesWith = -1; + for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { + //If the identifier of an existing track and the current track match, assume the are the same track and reject the negotiated one. + ///\todo Maybe switch to a new form of detecting collisions, especially with regards to multiple audio languages and camera angles. + if (it->second.getIdentifier() == trackIdentifier) { + collidesWith = it->first; + break; + } + } + /*LTS-END*/ //Remove the "negotiate" status in either case negotiatingTracks.erase(value); @@ -338,15 +468,49 @@ namespace Mist { metaPages[value].master = true; metaPages.erase(value); - int finalMap = (trackMeta.tracks.find(value)->second.type == "video" ? 1 : 2); + //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; + } + /*LTS-START*/ + unsigned long 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; + DEBUG_MSG(DLVL_DEVEL, "No colision detected for temporary track %lu from user %u, assigning final track number %lu", value, id, finalMap); + } + /*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) { - INFO_MSG("Resume of track %d detected, coming from temporary track %lu of user %u", finalMap, value, id); + INFO_MSG("Resume of track %lu detected, coming from temporary track %lu of user %u", finalMap, value, id); } else { - INFO_MSG("New track detected, assigned track id %d, coming from temporary track %lu of user %u", finalMap, value, id); + INFO_MSG("New track detected, assigned track id %lu, coming from temporary track %lu of user %u", finalMap, value, id); } + } else { + //Otherwise replace existing track + INFO_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 + metaPages[finalMap].master = true; + 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. + lastUpdated[finalMap] = Util::bootSecs(); + //Register the user thats is pushing this element + pushLocation[finalMap] = thisData; + //Initialize the metadata for this track if it was not in place yet. + if (!myMeta.tracks.count(finalMap)) { + DEBUG_MSG(DLVL_HIGH, "Inserting metadata for track number %lu", finalMap); + myMeta.tracks[finalMap] = trackMeta.tracks.begin()->second; + myMeta.tracks[finalMap].trackID = finalMap; } //Register the new track as an active track. @@ -456,7 +620,8 @@ namespace Mist { lastUpdated[tNum] = Util::bootSecs(); while (tmpPack) { //Update the metadata with this packet - myMeta.update(tmpPack); + ///\todo Why is there an LTS tag here? + myMeta.update(tmpPack, segmentSize);/*LTS*/ //Set the first time when appropriate if (pageData.firstTime == 0) { pageData.firstTime = tmpPack.getTime(); @@ -469,6 +634,7 @@ namespace Mist { } bool inputBuffer::setup() { + lastReTime = Util::epoch(); /*LTS*/ std::string strName = config->getString("streamname"); Util::sanitizeName(strName); strName = strName.substr(0, (strName.find_first_of("+ "))); @@ -496,6 +662,74 @@ namespace Mist { 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(); + } + } + //if the new value is different, print a message and apply it + if (cutTime != tmpNum) { + DEBUG_MSG(DLVL_DEVEL, "Setting cutTime from %u to new value of %lli", cutTime, tmpNum); + cutTime = 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 the new value is different, print a message and apply it + if (segmentSize != tmpNum) { + DEBUG_MSG(DLVL_DEVEL, "Setting segmentSize from %u to new value of %lli", segmentSize, tmpNum); + segmentSize = tmpNum; + } + + //if stream is configured and setting is present, use it, always + std::string rec; + if (streamCfg && streamCfg.getMember("record")) { + rec = streamCfg.getMember("record").asInt(); + } else { + if (streamCfg) { + //otherwise, if stream is configured use the default + rec = config->getOption("record", true)[0u].asString(); + } else { + //if not, use the commandline argument + rec = config->getOption("record").asString(); + } + } + //if the new value is different, print a message and apply it + if (recName != rec) { + //close currently recording file, for we should open a new one + DEBUG_MSG(DLVL_DEVEL, "Stopping recording of %s to %s", config->getString("streamname").c_str(), recName.c_str()); + recFile.close(); + recMeta.tracks.clear(); + recName = rec; + } + if (recName != "" && !recFile.is_open()) { + DEBUG_MSG(DLVL_DEVEL, "Starting recording of %s to %s", config->getString("streamname").c_str(), recName.c_str()); + recFile.open(recName.c_str()); + if (recFile.fail()) { + DEBUG_MSG(DLVL_DEVEL, "Error occured during record opening: %s", strerror(errno)); + } + recBpos = 0; + } + /*LTS-END*/ configLock.post(); configLock.close(); return true; diff --git a/src/input/input_buffer.h b/src/input/input_buffer.h index e6e96af8..faa84673 100644 --- a/src/input/input_buffer.h +++ b/src/input/input_buffer.h @@ -1,3 +1,5 @@ +#include + #include "input.h" #include #include @@ -10,6 +12,8 @@ namespace Mist { private: unsigned int bufferTime; unsigned int cutTime; + unsigned int segmentSize; /*LTS*/ + unsigned int lastReTime; /*LTS*/ protected: //Private Functions bool setup(); @@ -31,6 +35,11 @@ namespace Mist { std::map > bufferLocations; std::map pushLocation; inputBuffer * singleton; + + std::string recName;/*LTS*/ + DTSC::Meta recMeta;/*LTS*/ + std::ofstream recFile;/*LTS*/ + long long int recBpos;/*LTS*/ }; } diff --git a/src/input/input_folder.cpp b/src/input/input_folder.cpp new file mode 100644 index 00000000..cc1736a6 --- /dev/null +++ b/src/input/input_folder.cpp @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "input_folder.h" + +namespace Mist { + inputFolder::inputFolder(Util::Config * cfg) : Input(cfg) { + capa["name"] = "Folder"; + capa["decs"] = "Folder input, re-starts itself as the appropiate input."; + capa["source_match"] = "/*/"; + capa["priority"] = 9ll; + } +} diff --git a/src/input/input_folder.h b/src/input/input_folder.h new file mode 100644 index 00000000..db73f916 --- /dev/null +++ b/src/input/input_folder.h @@ -0,0 +1,14 @@ +#include "input.h" +#include + +namespace Mist { + class inputFolder : public Input { + public: + inputFolder(Util::Config * cfg); + protected: + bool setup(){return false;}; + bool readHeader(){return false;}; + }; +} + +typedef Mist::inputFolder mistIn; diff --git a/src/input/input_ismv.cpp b/src/input/input_ismv.cpp new file mode 100644 index 00000000..e6ce6ac5 --- /dev/null +++ b/src/input/input_ismv.cpp @@ -0,0 +1,407 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "input_ismv.h" + +namespace Mist { + inputISMV::inputISMV(Util::Config * cfg) : Input(cfg) { + capa["name"] = "ISMV"; + capa["decs"] = "Enables ISMV Input"; + capa["source_match"] = "/*.ismv"; + capa["priority"] = 9ll; + capa["codecs"][0u][0u].append("H264"); + capa["codecs"][0u][1u].append("AAC"); + + inFile = 0; + } + + bool inputISMV::setup() { + if (config->getString("input") == "-") { + std::cerr << "Input from stdin not yet supported" << std::endl; + return false; + } + 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 = fopen(config->getString("input").c_str(), "r"); + if (!inFile) { + return false; + } + return true; + } + + bool inputISMV::readHeader() { + if (!inFile) { + return false; + } + //See whether a separate header file exists. + DTSC::File tmp(config->getString("input") + ".dtsh"); + if (tmp) { + myMeta = tmp.getMeta(); + return true; + } + //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); + //parse fragments form here + while (parseFrag(tId, trunSamples, initVecs, mdat)) { + if (!currentDuration.count(tId)) { + currentDuration[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) { + lastPack["interframe"] = 1LL; + lastBytePos ++; + } else { + lastPack["keyframe"] = 1LL; + lastBytePos = curBytePos; + } + lastPack["bpos"] = lastBytePos; + lastPack["nalu"] = 1LL; + unsigned int offsetConv = trunSamples[i].sampleOffset / 10000; + lastPack["offset"] = *(int*)&offsetConv; + } else { + if (i == 0) { + lastPack["keyframe"] = 1LL; + lastPack["bpos"] = curBytePos; + } + } + myMeta.update(lastPack); + currentDuration[tId] += trunSamples[i].sampleDuration; + currOffset += trunSamples[i].sampleSize; + i ++; + } + curBytePos = ftell(inFile); + } + std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str()); + oFile << myMeta.toJSON().toNetPacked(); + oFile.close(); + 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"] = (long long int)(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"] = 1LL; + } else { + thisPack["interframe"] = 1LL; + } + thisPack["nalu"] = 1LL; + thisPack["offset"] = buffered.begin()->offset / 10000; + } else { + if (buffered.begin()->isKeyFrame) { + thisPack["keyframe"] = 1LL; + } + } + thisPack["bpos"] = buffered.begin()->position; + 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]++; + } + } + std::string tmpStr = thisPack.toNetPacked(); + thisPacket.reInit(tmpStr.data(), tmpStr.size()); + } + + ///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number + bool inputISMV::atKeyFrame(){ + return thisPacket.getFlag("keyframe"); + } + + void inputISMV::seek(int seekTime) { + 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; + } + } + + 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 = ""; + } + } + 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"; + } + } + } + } + } + } + } + } + } + } + } + } + } + + bool inputISMV::parseFrag(int & tId, std::vector & trunSamples, std::vector & initVecs, std::string & mdat) { + tId = -1; + 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*/ + } + } + } + readBox("mdat", mdat); + if (mdat ==""){ + return false; + } + return true; + } + + 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); + 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*/ + } + } + } + lastPos = ftell(inFile) + 8; + for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){ + seekPos myPos; + myPos.position = lastPos; + myPos.trackId = trackId; + myPos.time = lastTime; + myPos.duration = trunBox.getSampleInformation(i).sampleDuration; + myPos.size = trunBox.getSampleInformation(i).sampleSize; + if( trunBox.getFlags() & MP4::trunsampleOffsets){ + unsigned int offsetConv = trunBox.getSampleInformation(i).sampleOffset; + myPos.offset = *(int*)&offsetConv; + }else{ + 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; + 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); + } +} + + + + + diff --git a/src/input/input_ismv.h b/src/input/input_ismv.h new file mode 100644 index 00000000..a7843d0d --- /dev/null +++ b/src/input/input_ismv.h @@ -0,0 +1,51 @@ +#include "input.h" +#include +#include +#include +#include +#include + +namespace Mist { + struct seekPos { + bool operator < (const seekPos & rhs) const { + 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; + bool isKeyFrame; + std::string iVec; + }; + + + class inputISMV : public Input { + public: + inputISMV(Util::Config * cfg); + protected: + //Private Functions + bool setup(); + bool readHeader(); + void getNext(bool smart = true); + void seek(int seekTime); + void trackSelect(std::string trackSpec); + bool atKeyFrame(); + + 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); + std::set buffered; + std::map lastKeyNum; + }; +} + +typedef Mist::inputISMV mistIn; + diff --git a/src/input/input_mp3.cpp b/src/input/input_mp3.cpp index 3cfa23be..7cf0b21d 100644 --- a/src/input/input_mp3.cpp +++ b/src/input/input_mp3.cpp @@ -122,7 +122,7 @@ namespace Mist { } } if (!offset){ - DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %llu", filePos); + DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %lu", filePos); return; } filePos += offset; diff --git a/src/input/input_mp4.cpp b/src/input/input_mp4.cpp new file mode 100644 index 00000000..278ed740 --- /dev/null +++ b/src/input/input_mp4.cpp @@ -0,0 +1,632 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "input_mp4.h" + +namespace Mist { + + mp4TrackHeader::mp4TrackHeader(){ + initialised = false; + stscStart = 0; + sampleIndex = 0; + deltaIndex = 0; + deltaPos = 0; + deltaTotal = 0; + offsetIndex = 0; + offsetPos = 0; + sttsBox.clear(); + cttsBox.clear(); + stszBox.clear(); + stcoBox.clear(); + } + + long unsigned int mp4TrackHeader::size(){ + if (!stszBox.asBox()){ + return 0; + }else{ + return stszBox.getSampleCount(); + } + } + + void mp4TrackHeader::read(MP4::TRAK & trakBox){ + initialised = false; + std::string tmp;//temporary string for copying box data + MP4::Box trakLoopPeek; + timeScale = 1; + //for all in trak + for (uint32_t j = 0; j < trakBox.getContentCount(); j++){ + trakLoopPeek = MP4::Box(trakBox.getContent(j).asBox(),false); + std::string trakBoxType = trakLoopPeek.getType(); + if (trakBoxType == "mdia"){//fi tkhd & if mdia + MP4::Box mdiaLoopPeek; + //for all in mdia + for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){ + mdiaLoopPeek = MP4::Box(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),false); + std::string mdiaBoxType = mdiaLoopPeek.getType(); + if (mdiaBoxType == "mdhd"){ + timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale(); + }else if (mdiaBoxType == "minf"){//fi hdlr + //for all in minf + //get all boxes: stco stsz,stss + MP4::Box minfLoopPeek; + for (uint32_t l = 0; l < ((MP4::MINF&)mdiaLoopPeek).getContentCount(); l++){ + minfLoopPeek = MP4::Box(((MP4::MINF&)mdiaLoopPeek).getContent(l).asBox(),false); + std::string minfBoxType = minfLoopPeek.getType(); + ///\todo more stuff here + if (minfBoxType == "stbl"){ + MP4::Box stblLoopPeek; + for (uint32_t m = 0; m < ((MP4::STBL&)minfLoopPeek).getContentCount(); m++){ + stblLoopPeek = MP4::Box(((MP4::STBL&)minfLoopPeek).getContent(m).asBox(),false); + std::string stblBoxType = stblLoopPeek.getType(); + if (stblBoxType == "stts"){ + tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); + sttsBox.clear(); + sttsBox.read(tmp); + }else if (stblBoxType == "ctts"){ + tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); + cttsBox.clear(); + cttsBox.read(tmp); + }else if (stblBoxType == "stsz"){ + tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); + stszBox.clear(); + stszBox.read(tmp); + }else if (stblBoxType == "stco" || stblBoxType == "co64"){ + tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); + stcoBox.clear(); + stcoBox.read(tmp); + }else if (stblBoxType == "stsc"){ + tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); + stscBox.clear(); + stscBox.read(tmp); + } + }//rof stbl + }//fi stbl + }//rof minf + }//fi minf + }//rof mdia + }//fi mdia + }//rof trak + } + + void mp4TrackHeader::getPart(long unsigned int index, long long unsigned int & offset,unsigned int& size, long long unsigned int & timestamp, long long unsigned int & timeOffset){ + + + if (index < sampleIndex){ + sampleIndex = 0; + stscStart = 0; + } + + while (stscStart < stscBox.getEntryCount()){ + //check where the next index starts + unsigned long long nextSampleIndex; + if (stscStart + 1 < stscBox.getEntryCount()){ + nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart+1).firstChunk - stscBox.getSTSCEntry(stscStart).firstChunk) * stscBox.getSTSCEntry(stscStart).samplesPerChunk; + }else{ + nextSampleIndex = stszBox.getSampleCount(); + } + //if the next one is too far, we're in the right spot + if (nextSampleIndex > index){ + break; + } + sampleIndex = nextSampleIndex; + ++stscStart; + } + + if (sampleIndex > index){ + FAIL_MSG("Could not complete seek - not in file (%llu > %lu)", sampleIndex, index); + } + + long long unsigned stcoPlace = (stscBox.getSTSCEntry(stscStart).firstChunk - 1 ) + ((index - sampleIndex) / stscBox.getSTSCEntry(stscStart).samplesPerChunk); + long long unsigned stszStart = sampleIndex + (stcoPlace - (stscBox.getSTSCEntry(stscStart).firstChunk - 1)) * stscBox.getSTSCEntry(stscStart).samplesPerChunk; + + //set the offset to the correct value + if (stcoBox.getType() == "co64"){ + offset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoPlace); + }else{ + offset = stcoBox.getChunkOffset(stcoPlace); + } + + for (int j = stszStart; j < index; j++){ + offset += stszBox.getEntrySize(j); + } + + if (index < deltaPos){ + deltaIndex = 0; + deltaPos = 0; + deltaTotal = 0; + } + + MP4::STTSEntry tmpSTTS; + while (deltaIndex < sttsBox.getEntryCount()){ + tmpSTTS = sttsBox.getSTTSEntry(deltaIndex); + if ((index - deltaPos) < tmpSTTS.sampleCount){ + break; + }else{ + deltaTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta; + deltaPos += tmpSTTS.sampleCount; + } + ++deltaIndex; + } + timestamp = ((deltaTotal + ((index-deltaPos) * tmpSTTS.sampleDelta))*1000) / timeScale; + initialised = true; + + if (index < offsetPos){ + offsetIndex = 0; + offsetPos = 0; + } + MP4::CTTSEntry tmpCTTS; + while (offsetIndex < cttsBox.getEntryCount()){ + tmpCTTS = cttsBox.getCTTSEntry(offsetIndex); + if ((index - offsetPos) < tmpCTTS.sampleCount){ + timeOffset = (tmpCTTS.sampleOffset*1000)/timeScale; + break; + } + offsetPos += tmpCTTS.sampleCount; + ++offsetIndex; + } + + //next lines are common for next-getting and seeking + size = stszBox.getEntrySize(index); + + } + + inputMP4::inputMP4(Util::Config * cfg) : Input(cfg) { + malSize = 4;//initialise data read buffer to 0; + data = (char*)malloc(malSize); + capa["name"] = "MP4"; + capa["decs"] = "Enables MP4 Input"; + capa["source_match"] = "/*.mp4"; + capa["priority"] = 9ll; + capa["codecs"][0u][0u].append("HEVC"); + capa["codecs"][0u][0u].append("H264"); + capa["codecs"][0u][0u].append("H263"); + capa["codecs"][0u][0u].append("VP6"); + capa["codecs"][0u][1u].append("AAC"); + capa["codecs"][0u][1u].append("AC3"); + capa["codecs"][0u][1u].append("MP3"); + } + + inputMP4::~inputMP4(){ + free(data); + } + + bool inputMP4::setup() { + if (config->getString("input") == "-") { + std::cerr << "Input from stdin not yet supported" << std::endl; + return false; + } + 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 = fopen(config->getString("input").c_str(), "r"); + if (!inFile) { + return false; + } + return true; + + } + + bool inputMP4::readHeader() { + if (!inFile) { + INFO_MSG("inFile failed!"); + return false; + } + //make trackmap here from inFile + long long unsigned int trackNo = 0; + + std::string tmp;//temp string for reading boxes. + + + //first we get the necessary header parts + while(!feof(inFile)){ + std::string boxType = MP4::readBoxType(inFile); + if (boxType=="moov"){ + MP4::MOOV moovBox; + moovBox.read(inFile); + //for all box in moov + + MP4::Box moovLoopPeek; + for (uint32_t i = 0; i < moovBox.getContentCount(); i++){ + tmp = std::string(moovBox.getContent(i).asBox() ,moovBox.getContent(i).boxedSize()); + moovLoopPeek.read(tmp); + //if trak + if (moovLoopPeek.getType() == "trak"){ + //create new track entry here + trackNo ++; + + headerData[trackNo].read((MP4::TRAK&)moovLoopPeek); + } + } + }else if (boxType == "erro"){ + break; + }else{ + if (!MP4::skipBox(inFile)){//moving on to next box + DEBUG_MSG(DLVL_FAIL,"Error in skipping box, exiting"); + return false; + } + }//fi moov + }//when at the end of the file + //seek file to 0; + fseeko(inFile,0,SEEK_SET); + + //See whether a separate header file exists. + DTSC::File tmpdtsh(config->getString("input") + ".dtsh"); + if (tmpdtsh){ + myMeta = tmpdtsh.getMeta(); + return true; + } + trackNo = 0; + std::set BPosSet; + //Create header file from MP4 data + while(!feof(inFile)){ + std::string boxType = MP4::readBoxType(inFile); + if (boxType=="moov"){ + MP4::MOOV moovBox; + moovBox.read(inFile); + //for all box in moov + MP4::Box moovLoopPeek; + for (uint32_t i = 0; i < moovBox.getContentCount(); i++){ + tmp = std::string(moovBox.getContent(i).asBox(), moovBox.getContent(i).boxedSize()); + moovLoopPeek.read(tmp); + //if trak + if (moovLoopPeek.getType() == "trak"){ + //create new track entry here + long long unsigned int trackNo = myMeta.tracks.size()+1; + myMeta.tracks[trackNo].trackID = trackNo; + MP4::Box trakLoopPeek; + unsigned long int timeScale = 1; + //for all in trak + for (uint32_t j = 0; j < ((MP4::TRAK&)moovLoopPeek).getContentCount(); j++){ + tmp = std::string(((MP4::MOOV&)moovLoopPeek).getContent(j).asBox(),((MP4::MOOV&)moovLoopPeek).getContent(j).boxedSize()); + trakLoopPeek.read(tmp); + std::string trakBoxType = trakLoopPeek.getType(); + //note: per track: trackID codec, type (vid/aud), init + //if tkhd + if (trakBoxType == "tkhd"){ + MP4::TKHD tkhdBox(0,0,0,0);///\todo: this can be done with casting + tmp = std::string(trakLoopPeek.asBox(), trakLoopPeek.boxedSize()); + tkhdBox.read(tmp); + //remember stuff for decoding stuff later + if (tkhdBox.getWidth() > 0){ + myMeta.tracks[trackNo].width = tkhdBox.getWidth(); + myMeta.tracks[trackNo].height = tkhdBox.getHeight(); + } + }else if (trakBoxType == "mdia"){//fi tkhd & if mdia + MP4::Box mdiaLoopPeek; + //for all in mdia + for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){ + tmp = std::string(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),((MP4::MDIA&)trakLoopPeek).getContent(k).boxedSize()); + mdiaLoopPeek.read(tmp); + std::string mdiaBoxType = mdiaLoopPeek.getType(); + if (mdiaBoxType == "mdhd"){ + timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale(); + }else if (mdiaBoxType == "hdlr"){//fi mdhd + std::string handlerType = ((MP4::HDLR&)mdiaLoopPeek).getHandlerType(); + if (handlerType != "vide" && handlerType !="soun"){ + myMeta.tracks.erase(trackNo); + //skip meta boxes for now + break; + } + }else if (mdiaBoxType == "minf"){//fi hdlr + //for all in minf + //get all boxes: stco stsz,stss + MP4::Box minfLoopPeek; + for (uint32_t l = 0; l < ((MP4::MINF&)mdiaLoopPeek).getContentCount(); l++){ + tmp = std::string(((MP4::MINF&)mdiaLoopPeek).getContent(l).asBox(),((MP4::MINF&)mdiaLoopPeek).getContent(l).boxedSize()); + minfLoopPeek.read(tmp); + std::string minfBoxType = minfLoopPeek.getType(); + ///\todo more stuff here + if (minfBoxType == "stbl"){ + MP4::Box stblLoopPeek; + MP4::STSS stssBox; + MP4::STTS sttsBox; + MP4::STSZ stszBox; + MP4::STCO stcoBox; + MP4::STSC stscBox; + for (uint32_t m = 0; m < ((MP4::STBL&)minfLoopPeek).getContentCount(); m++){ + tmp = std::string(((MP4::STBL&)minfLoopPeek).getContent(m).asBox(),((MP4::STBL&)minfLoopPeek).getContent(m).boxedSize()); + std::string stboxRead = tmp; + stblLoopPeek.read(tmp); + std::string stblBoxType = stblLoopPeek.getType(); + if (stblBoxType == "stss"){ + stssBox.read(stboxRead); + }else if (stblBoxType == "stts"){ + sttsBox.read(stboxRead); + }else if (stblBoxType == "stsz"){ + stszBox.read(stboxRead); + }else if (stblBoxType == "stco" || stblBoxType == "co64"){ + stcoBox.read(stboxRead); + }else if (stblBoxType == "stsc"){ + stscBox.read(stboxRead); + }else if (stblBoxType == "stsd"){ + //check for codec in here + MP4::Box & tmpBox = ((MP4::STSD&)stblLoopPeek).getEntry(0); + std::string tmpType = tmpBox.getType(); + INFO_MSG("Found track of type %s", tmpType.c_str()); + if (tmpType == "avc1" || tmpType == "h264" || tmpType == "mp4v"){ + myMeta.tracks[trackNo].type = "video"; + myMeta.tracks[trackNo].codec = "H264"; + myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth(); + myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight(); + MP4::Box tmpBox2 = tmpBox; + MP4::Box tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getCLAP(); + if (tmpContent.getType() == "avcC"){ + myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize()); + } + tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getPASP(); + if (tmpContent.getType() == "avcC"){ + myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize()); + } + }else if (tmpType == "hev1"){ + myMeta.tracks[trackNo].type = "video"; + myMeta.tracks[trackNo].codec = "HEVC"; + myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth(); + myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight(); + MP4::Box tmpBox2 = ((MP4::VisualSampleEntry&)tmpBox).getCLAP(); + myMeta.tracks[trackNo].init = std::string(tmpBox2.payload(),tmpBox2.payloadSize()); + }else if (tmpType == "mp4a" || tmpType == "aac " || tmpType == "ac-3"){ + myMeta.tracks[trackNo].type = "audio"; + myMeta.tracks[trackNo].channels = ((MP4::AudioSampleEntry&)tmpBox).getChannelCount(); + myMeta.tracks[trackNo].rate = (long long int)(((MP4::AudioSampleEntry&)tmpBox).getSampleRate()); + if (tmpType == "ac-3"){ + myMeta.tracks[trackNo].codec = "AC3"; + }else{ + MP4::Box esds = ((MP4::AudioSampleEntry&)tmpBox).getCodecBox(); + if (((MP4::ESDS&)esds).isAAC()){ + myMeta.tracks[trackNo].codec = "AAC"; + myMeta.tracks[trackNo].init = ((MP4::ESDS&)esds).getInitData(); + }else{ + myMeta.tracks[trackNo].codec = "MP3"; + } + } + myMeta.tracks[trackNo].size = 16;///\todo this might be nice to calculate from mp4 file; + //get Visual sample entry -> esds -> startcodes + }else{ + myMeta.tracks.erase(trackNo); + } + } + }//rof stbl + uint64_t totaldur = 0;///\todo note: set this to begin time + mp4PartBpos BsetPart; + long long unsigned int entryNo = 0; + long long unsigned int sampleNo = 0; + MP4::STTSEntry tempSTTS; + tempSTTS = sttsBox.getSTTSEntry(entryNo); + long long unsigned int curSTSS = 0; + bool vidTrack = (myMeta.tracks[trackNo].type == "video"); + //change to for over all samples + unsigned int stcoIndex = 0; + unsigned int stscIndex = 0; + unsigned int fromSTCOinSTSC = 0; + long long unsigned int tempOffset; + bool stcoIs64 = (stcoBox.getType() == "co64"); + if (stcoIs64){ + tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(0); + }else{ + tempOffset = stcoBox.getChunkOffset(0); + } + long long unsigned int nextFirstChunk; + if (stscBox.getEntryCount() > 1){ + nextFirstChunk = stscBox.getSTSCEntry(1).firstChunk - 1; + }else{ + if (stcoIs64){ + nextFirstChunk = ((MP4::CO64*)&stcoBox)->getEntryCount(); + }else{ + nextFirstChunk = stcoBox.getEntryCount(); + } + } + for(long long unsigned int sampleIndex = 0; sampleIndex < stszBox.getSampleCount(); sampleIndex ++){ + if (stcoIndex >= nextFirstChunk){//note + stscIndex ++; + if (stscIndex + 1 < stscBox.getEntryCount()){ + nextFirstChunk = stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1; + }else{ + if (stcoIs64){ + nextFirstChunk = ((MP4::CO64*)&stcoBox)->getEntryCount(); + }else{ + nextFirstChunk = stcoBox.getEntryCount(); + } + } + } + if (vidTrack && curSTSS < stssBox.getEntryCount() && sampleIndex + 1 == stssBox.getSampleNumber(curSTSS)){ + BsetPart.keyframe = true; + curSTSS ++; + }else{ + BsetPart.keyframe = false; + } + //in bpos set + BsetPart.stcoNr=stcoIndex; + //bpos = chunkoffset[samplenr] in stco + BsetPart.bpos = tempOffset; + fromSTCOinSTSC ++; + if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){//as long as we are still in this chunk + tempOffset += stszBox.getEntrySize(sampleIndex); + }else{ + stcoIndex ++; + fromSTCOinSTSC = 0; + if (stcoIs64){ + tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoIndex); + }else{ + tempOffset = stcoBox.getChunkOffset(stcoIndex); + } + } + //time = totaldur + stts[entry][sample] + BsetPart.time = (totaldur*1000)/timeScale; + totaldur += tempSTTS.sampleDelta; + sampleNo++; + if (sampleNo >= tempSTTS.sampleCount){ + entryNo++; + sampleNo = 0; + if (entryNo < sttsBox.getEntryCount()){ + tempSTTS = sttsBox.getSTTSEntry(entryNo); + } + } + //set size, that's easy + BsetPart.size = stszBox.getEntrySize(sampleIndex); + //trackid + BsetPart.trackID=trackNo; + BPosSet.insert(BsetPart); + }//while over stsc + if (vidTrack){ + //something wrong with the time formula, but the answer is right for some reason + /// \todo Fix this. This makes no sense whatsoever. + if (stcoIs64){ + myMeta.tracks[trackNo].fpks = (((double)(((MP4::CO64*)&stcoBox)->getEntryCount()*1000))/((totaldur*1000)/timeScale))*1000; + }else{ + myMeta.tracks[trackNo].fpks = (((double)(stcoBox.getEntryCount()*1000))/((totaldur*1000)/timeScale))*1000; + } + } + }//fi stbl + }//rof minf + }//fi minf + }//rof mdia + }//fi mdia + }//rof trak + }//endif trak + }//rof moov + }else if (boxType == "erro"){ + break; + }else{ + if (!MP4::skipBox(inFile)){//moving on to next box + DEBUG_MSG(DLVL_FAIL,"Error in Skipping box, exiting"); + return false; + } + }// if moov + }// end while file read + //for all in bpos set, find its data + clearerr(inFile); + + for (std::set::iterator it = BPosSet.begin(); it != BPosSet.end(); it++){ + if (!fseeko(inFile,it->bpos,SEEK_SET)){ + if (it->size > malSize){ + data = (char*)realloc(data, it->size); + malSize = it->size; + } + int tmp = fread(data, it->size, 1, inFile); + if (tmp == 1){ + //add data + myMeta.update(it->time, 1, it->trackID, it->size, it->bpos, it->keyframe); + }else{ + INFO_MSG("fread did not return 1, bpos: %llu size: %llu keyframe: %d error: %s", it->bpos, it->size, it->keyframe, strerror(errno)); + return false; + } + }else{ + INFO_MSG("fseek failed!"); + return false; + } + }//rof bpos set + //outputting dtsh file + std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str()); + oFile << myMeta.toJSON().toNetPacked(); + oFile.close(); + return true; + } + + void inputMP4::getNext(bool smart) {//get next part from track in stream + if (curPositions.empty()){ + thisPacket.null(); + return; + } + //pop uit set + mp4PartTime curPart = *curPositions.begin(); + curPositions.erase(curPositions.begin()); + + bool isKeyframe = false; + if(nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){ + //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()){ + 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 (fseeko(inFile,curPart.bpos,SEEK_SET)){ + DEBUG_MSG(DLVL_FAIL,"seek unsuccessful; bpos: %llu error: %s",curPart.bpos, strerror(errno)); + thisPacket.null(); + return; + } + if (curPart.size > malSize){ + data = (char*)realloc(data, curPart.size); + malSize = curPart.size; + } + if (fread(data, curPart.size, 1, inFile)!=1){ + DEBUG_MSG(DLVL_FAIL,"read unsuccessful at %ld", ftell(inFile)); + thisPacket.null(); + return; + } + thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, 0/*Note: no bpos*/, 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); + curPositions.insert(curPart); + } + } + + void inputMP4::seek(int seekTime) {//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); + //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 + } + + 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 = ""; + } + } + } +} + diff --git a/src/input/input_mp4.h b/src/input/input_mp4.h new file mode 100644 index 00000000..24a530a0 --- /dev/null +++ b/src/input/input_mp4.h @@ -0,0 +1,102 @@ +#include "input.h" +#include +#include +#include +namespace Mist { + class mp4PartTime{ + public: + mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0) {} + bool operator < (const mp4PartTime & rhs) const { + if (time < rhs.time){ + return true; + }else{ + if (time == rhs.time){ + if (trackID < rhs.trackID){ + return true; + } + } + } + return false; + } + long long unsigned int time; + long long unsigned int offset; + unsigned int trackID; + long long unsigned int bpos; + unsigned int size; + long unsigned int index; + }; + + struct mp4PartBpos{ + bool operator < (const mp4PartBpos & rhs) const { + if (time < rhs.time){ + return true; + }else{ + if (time == rhs.time){ + if (trackID < rhs.trackID){ + return true; + } + } + } + return false; + } + long long unsigned int time; + long long unsigned int trackID; + long long unsigned int bpos; + long long unsigned int size; + long long unsigned int stcoNr; + bool keyframe; + }; + + class mp4TrackHeader{ + public: + mp4TrackHeader(); + void read(MP4::TRAK & trakBox); + MP4::STCO stcoBox; + MP4::STSZ stszBox; + MP4::STTS sttsBox; + MP4::CTTS cttsBox; + MP4::STSC stscBox; + long unsigned int timeScale; + void getPart(long unsigned int index, long long unsigned int & offset,unsigned int& size, long long unsigned int & timestamp, long long unsigned int & timeOffset); + long unsigned int size(); + private: + bool initialised; + //next variables are needed for the stsc/stco loop + long long unsigned int stscStart; + long long unsigned int sampleIndex; + //next variables are needed for the stts loop + long long unsigned deltaIndex;///< Index in STTS box + long long unsigned deltaPos;///< Sample counter for STTS box + long long unsigned deltaTotal;///< Total timestamp for STTS box + //for CTTS box loop + long long unsigned offsetIndex;///< Index in CTTS box + long long unsigned offsetPos;///< Sample counter for CTTS box + }; + + class inputMP4 : public Input { + public: + inputMP4(Util::Config * cfg); + ~inputMP4(); + protected: + //Private Functions + bool setup(); + bool readHeader(); + void getNext(bool smart = true); + void seek(int seekTime); + void trackSelect(std::string trackSpec); + + FILE * inFile; + + std::map headerData; + std::set curPositions; + + //remember last seeked keyframe; + std::map nextKeyframe; + + //these next two variables keep a buffer for reading from filepointer inFile; + unsigned long long int malSize; + char* data;///\todo rename this variable to a more sensible name, it is a temporary piece of memory to read from files + }; +} + +typedef Mist::inputMP4 mistIn; diff --git a/src/input/input_ts.cpp b/src/input/input_ts.cpp new file mode 100755 index 00000000..d2ddf6c2 --- /dev/null +++ b/src/input/input_ts.cpp @@ -0,0 +1,516 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "input_ts.h" + +namespace Mist { + + + /// Constructor of TS Input + /// \arg cfg Util::Config that contains all current configurations. + inputTS::inputTS(Util::Config * cfg) : Input(cfg) { + capa["name"] = "TS"; + capa["decs"] = "Enables TS Input"; + capa["source_match"] = "/*.ts"; + capa["priority"] = 9ll; + capa["codecs"][0u][0u].append("H264"); + capa["codecs"][0u][1u].append("AAC"); + capa["codecs"][0u][1u].append("AC3"); + } + + ///Setup of TS Input + bool inputTS::setup() { + if (config->getString("input") == "-") { + inFile = stdin; + }else{ + inFile = fopen(config->getString("input").c_str(), "r"); + } + + if (config->getString("output") != "-") { + std::cerr << "Output to non-stdout not yet supported" << std::endl; + } + if (!inFile) { + return false; + } + return true; + } + + ///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 = ""; + } + } + } + + void inputTS::parsePESHeader(int tid, pesBuffer & buf){ + if (buf.data.size() < 9){ + return; + } + if (buf.data.size() < 9 + buf.data[8]){ + return; + } + if( (((int)buf.data[0] << 16) | ((int)buf.data[1] << 8) | buf.data[2]) != 0x000001){ + DEBUG_MSG(DLVL_WARN, "Parsing PES for track %d failed due to incorrect header (%0.6X), throwing away", tid, (((int)buf.data[0] << 16) | ((int)buf.data[1] << 8) | buf.data[2]) ); + buf.data = ""; + return; + } + buf.len = (((int)buf.data[4] << 8) | buf.data[5]) - (3 + buf.data[8]); + if ((buf.data[7] >> 6) & 0x02){//Check for PTS presence + buf.time = ((buf.data[9] >> 1) & 0x07); + buf.time <<= 15; + buf.time |= ((int)buf.data[10] << 7) | ((buf.data[11] >> 1) & 0x7F); + buf.time <<= 15; + buf.time |= ((int)buf.data[12] << 7) | ((buf.data[13] >> 1) & 0x7F); + buf.time /= 90; + if (((buf.data[7] & 0xC0) >> 6) & 0x01){//Check for DTS presence (yes, only if PTS present) + buf.offset = buf.time; + buf.time = ((buf.data[14] >> 1) & 0x07); + buf.time <<= 15; + buf.time |= ((int)buf.data[15] << 7) | ((buf.data[16] >> 1) & 0x7F); + buf.time <<= 15; + buf.time |= ((int)buf.data[17] << 7) | ((buf.data[18] >> 1) & 0x7F); + buf.time /= 90; + buf.offset -= buf.time; + } + } + if (!firstTimes.count(tid)){ + firstTimes[tid] = buf.time; + } + buf.time -= firstTimes[tid]; + buf.data.erase(0, 9 + buf.data[8]); + } + + void inputTS::parsePESPayload(int tid, pesBuffer & buf){ + if (myMeta.tracks[tid].codec == "H264"){ + parseH264PES(tid, buf); + } + if (myMeta.tracks[tid].codec == "AAC"){ + parseAACPES(tid, buf); + } + } + + + void inputTS::parseAACPES(int tid, pesBuffer & buf){ + if (!buf.data.size()){ + buf.len = 0; + return; + } + if (myMeta.tracks[tid].init == ""){ + char audioInit[2];//5 bits object type, 4 bits frequency index, 4 bits channel index + char AACProfile = ((buf.data[2] >> 6) & 0x03) + 1; + char frequencyIndex = ((buf.data[2] >> 2) & 0x0F); + char channelConfig = ((buf.data[2] & 0x01) << 2) | ((buf.data[3] >> 6) & 0x03); + switch(frequencyIndex){ + case 0: myMeta.tracks[tid].rate = 96000; break; + case 1: myMeta.tracks[tid].rate = 88200; break; + case 2: myMeta.tracks[tid].rate = 64000; break; + case 3: myMeta.tracks[tid].rate = 48000; break; + case 4: myMeta.tracks[tid].rate = 44100; break; + case 5: myMeta.tracks[tid].rate = 32000; break; + case 6: myMeta.tracks[tid].rate = 24000; break; + case 7: myMeta.tracks[tid].rate = 22050; break; + case 8: myMeta.tracks[tid].rate = 16000; break; + case 9: myMeta.tracks[tid].rate = 12000; break; + case 10: myMeta.tracks[tid].rate = 11025; break; + case 11: myMeta.tracks[tid].rate = 8000; break; + case 12: myMeta.tracks[tid].rate = 7350; break; + default: myMeta.tracks[tid].rate = 0; break; + } + myMeta.tracks[tid].channels = channelConfig; + if (channelConfig == 7){ + myMeta.tracks[tid].channels = 8; + } + audioInit[0] = ((AACProfile & 0x1F) << 3) | ((frequencyIndex & 0x0E) >> 1); + audioInit[1] = ((frequencyIndex & 0x01) << 7) | ((channelConfig & 0x0F) << 3); + myMeta.tracks[tid].init = std::string(audioInit, 2); + //\todo This value is right now hardcoded, maybe fix this when we know how + myMeta.tracks[tid].size = 16; + } + buf.len = (((buf.data[3] & 0x03) << 11) | (buf.data[4] << 3) | ((buf.data[5] >> 5) & 0x07)) - (buf.data[1] & 0x01 ? 7 :9); + buf.curSampleCount += 1024 * ((buf.data[6] & 0x3) + 1);//Number of frames * samples per frame(1024); + buf.data.erase(0, (buf.data[1] & 0x01 ? 7 : 9));//Substract header + } + + void inputTS::parseH264PES(int tid, pesBuffer & buf){ + static char annexB[] = {0x00,0x00,0x01}; + static char nalLen[4]; + + int nalLength = 0; + std::string newData; + int pos = 0; + int nxtPos = buf.data.find(annexB, pos, 3); + //Rewrite buf.data from annexB to size-prefixed h.264 + while (nxtPos != std::string::npos){ + //Detect next packet (if any) and deduce current packet length + pos = nxtPos + 3; + nxtPos = buf.data.find(annexB, pos, 3); + if (nxtPos == std::string::npos){ + nalLength = buf.data.size() - pos; + }else{ + nalLength = nxtPos - pos; + if (buf.data[nxtPos - 1] == 0x00){//4-byte annexB header + nalLength--; + } + } + //Do nal type specific stuff + switch (buf.data[pos] & 0x1F){ + case 0x05: buf.isKey = true; break; + case 0x07: buf.sps = buf.data.substr(pos, nalLength); break; + case 0x08: buf.pps = buf.data.substr(pos, nalLength); break; + default: break; + } + if ((buf.data[pos] & 0x1F) != 0x07 && (buf.data[pos] & 0x1F) != 0x08 && (buf.data[pos] & 0x1F) != 0x09){ + //Append length + payload + nalLen[0] = (nalLength >> 24) & 0xFF; + nalLen[1] = (nalLength >> 16) & 0xFF; + nalLen[2] = (nalLength >> 8) & 0xFF; + nalLen[3] = nalLength & 0xFF; + newData.append(nalLen, 4); + newData += buf.data.substr(pos, nalLength); + } + } + buf.data = newData; + buf.len = newData.size(); + //If this packet had both a Sequence Parameter Set (sps) and a Picture Parameter Set (pps), calculate the metadata for the stream + if (buf.sps != "" && buf.pps != ""){ + MP4::AVCC avccBox; + avccBox.setVersion(1); + avccBox.setProfile(buf.sps[1]); + avccBox.setCompatibleProfiles(buf.sps[2]); + avccBox.setLevel(buf.sps[3]); + avccBox.setSPSNumber(1); + avccBox.setSPS(buf.sps); + avccBox.setPPSNumber(1); + avccBox.setPPS(buf.pps); + myMeta.tracks[tid].init = std::string(avccBox.payload(), avccBox.payloadSize()); + h264::SPS tmpNal(buf.sps, true); + h264::SPSMeta tmpMeta = tmpNal.getCharacteristics(); + myMeta.tracks[tid].width = tmpMeta.width; + myMeta.tracks[tid].height = tmpMeta.height; + myMeta.tracks[tid].fpks = tmpMeta.fps * 1000; + } + } + + ///Reads headers from a TS stream, and saves them into metadata + ///It works by going through the entire TS stream, and every time + ///It encounters a new PES start, it writes the currently found PES data + ///for a specific track to metadata. After the entire stream has been read, + ///it writes the remaining metadata. + ///\todo Find errors, perhaps parts can be made more modular + bool inputTS::readHeader(){ + if (!inFile) { + return false; + } + DTSC::File tmp(config->getString("input") + ".dtsh"); + if (tmp){ + myMeta = tmp.getMeta(); + return true; + } + + TS::Packet packet;//to analyse and extract data + fseek(inFile, 0, SEEK_SET);//seek to beginning + JSON::Value thisPacket; + + std::set PATIds; + std::map pidToType; + std::map lastBuffer; + + //h264::SPSmMta spsdata;//to analyse sps data, and extract resolution etc... + long long int lastBpos = 0; + while (packet.FromFile(inFile)){ + //Handle special packets (PAT/PMT) + if(packet.getPID() == 0x00){ + PATIds.clear(); + for (int i = 0; i < ((TS::ProgramAssociationTable&)packet).getProgramCount(); i++){ + PATIds.insert(((TS::ProgramAssociationTable&)packet).getProgramPID(i)); + } + } + if(PATIds.count(packet.getPID())){ + TS::ProgramMappingEntry entry = ((TS::ProgramMappingTable&)packet).getEntry(0); + while(entry){ + unsigned int pid = entry.getElementaryPid(); + pidToType[pid] = entry.getStreamType(); + //Check if the track exists in metadata + if (!myMeta.tracks.count(pid)){ + switch (entry.getStreamType()){ + case 0x1B: + myMeta.tracks[pid].codec = "H264"; + myMeta.tracks[pid].type = "video"; + myMeta.tracks[pid].trackID = pid; + break; + case 0x0F: + myMeta.tracks[pid].codec = "AAC"; + myMeta.tracks[pid].type = "audio"; + myMeta.tracks[pid].trackID = pid; + break; + case 0x81: + myMeta.tracks[pid].codec = "AC3"; + myMeta.tracks[pid].type = "audio"; + myMeta.tracks[pid].trackID = pid; + break; + default: + DEBUG_MSG(DLVL_WARN, "Ignoring unsupported track type %0.2X, on pid %d", entry.getStreamType(), pid); + break; + } + } + entry.advance(); + } + } + if(pidToType.count(packet.getPID())){ + //analyzing audio/video + //we have audio/video payload + //get trackID of this packet + int tid = packet.getPID(); + if (packet.getUnitStart() && lastBuffer.count(tid) && lastBuffer[tid].len){ + parsePESPayload(tid, lastBuffer[tid]); + thisPacket.null(); + thisPacket["data"] = lastBuffer[tid].data; + thisPacket["trackid"] = tid;//last trackid + thisPacket["bpos"] = lastBuffer[tid].bpos; + thisPacket["time"] = lastBuffer[tid].time ; + if (lastBuffer[tid].offset){ + thisPacket["offset"] = lastBuffer[tid].offset; + } + if (lastBuffer[tid].isKey){ + thisPacket["keyframe"] = 1LL; + } + myMeta.update(thisPacket);//metadata was read in + lastBuffer.erase(tid); + } + if (!lastBuffer.count(tid)){ + lastBuffer[tid] = pesBuffer(); + lastBuffer[tid].bpos = lastBpos; + } + lastBuffer[tid].data.append(packet.getPayload(), packet.getPayloadLength()); + if (!lastBuffer[tid].len){ + parsePESHeader(tid, lastBuffer[tid]); + } + if (lastBuffer[tid].data.size() == lastBuffer[tid].len) { + parsePESPayload(tid, lastBuffer[tid]); + if (myMeta.tracks[tid].codec == "AAC"){ + while(lastBuffer[tid].data.size()){ + thisPacket.null(); + thisPacket["data"] = lastBuffer[tid].data.substr(0, lastBuffer[tid].len); + thisPacket["trackid"] = tid;//last trackid + thisPacket["bpos"] = lastBuffer[tid].bpos; + thisPacket["time"] = lastBuffer[tid].time + (long long int)((double)((lastBuffer[tid].curSampleCount - 1024) * 1000)/ myMeta.tracks[tid].rate) ; + myMeta.update(thisPacket);//metadata was read in + lastBuffer[tid].data.erase(0, lastBuffer[tid].len); + parsePESPayload(tid, lastBuffer[tid]); + } + }else{ + thisPacket.null(); + thisPacket["data"] = lastBuffer[tid].data; + thisPacket["trackid"] = tid;//last trackid + thisPacket["bpos"] = lastBuffer[tid].bpos; + thisPacket["time"] = lastBuffer[tid].time ; + if (myMeta.tracks[tid].type == "video"){ + if (lastBuffer[tid].offset){ + thisPacket["offset"] = lastBuffer[tid].offset; + } + if (lastBuffer[tid].isKey){ + thisPacket["keyframe"] = 1LL; + } + } + myMeta.update(thisPacket);//metadata was read in + } + lastBuffer.erase(tid); + } + } + lastBpos = ftell(inFile); + } + + std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str()); + oFile << myMeta.toJSON().toNetPacked(); + oFile.close(); + return true; + } + + ///Reads a full PES packet, starting at the current byteposition + ///Assumes that you want a full PES for the first PID encountered + ///\todo Update to search for a specific PID + pesBuffer inputTS::readFullPES(int tid){ + pesBuffer pesBuf; + pesBuf.tid = tid; + if (feof(inFile)){ + DEBUG_MSG(DLVL_DEVEL, "Trying to read a PES past the end of the file, returning"); + return pesBuf; + } + unsigned int lastPos = ftell(inFile); + TS::Packet tsBuf; + tsBuf.FromFile(inFile); + //Find first PES start on the selected track + while (tsBuf.getPID() != tid || !tsBuf.getUnitStart()){ + lastPos = ftell(inFile); + tsBuf.FromFile(inFile); + if (feof(inFile)){ + return pesBuf; + } + } + pesBuf.bpos = lastPos; + pesBuf.data.append(tsBuf.getPayload(), tsBuf.getPayloadLength()); + parsePESHeader(tid, pesBuf); + bool unbound = false; + while (pesBuf.data.size() != pesBuf.len){ + //ReadNextPage + tsBuf.FromFile(inFile); + if (tsBuf.getPID() == tid && tsBuf.getUnitStart()){ + unbound = true; + break; + } + if (feof(inFile)){ + DEBUG_MSG(DLVL_DEVEL, "Reached EOF at an unexpected point... what happened?"); + return pesBuf; + } + if (tsBuf.getPID() == tid){ + pesBuf.data.append(tsBuf.getPayload(), tsBuf.getPayloadLength()); + pesBuf.lastPos = ftell(inFile); + } + if (pesBuf.len == 0){ + parsePESHeader(tid, pesBuf); + } + } + pesBuf.lastPos = ftell(inFile); + if (unbound){ + pesBuf.lastPos -= 188; + } + parsePESPayload(tid, pesBuf); + return pesBuf; + } + + ///Gets the next packet that is to be sent + ///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){ + static JSON::Value thisPack; + if ( !playbackBuf.size()){ + DEBUG_MSG(DLVL_WARN, "No seek positions set - returning empty packet."); + thisPacket.null(); + return; + } + + //Store current buffer + pesBuffer thisBuf = *playbackBuf.begin(); + playbackBuf.erase(playbackBuf.begin()); + + //Seek follow up + fseek(inFile, thisBuf.lastPos, SEEK_SET); + pesBuffer nxtBuf; + if (myMeta.tracks[thisBuf.tid].codec != "AAC" || playbackBuf.size() < 2){ + nxtBuf = readFullPES(thisBuf.tid); + } + if (nxtBuf.len){ + if (myMeta.tracks[nxtBuf.tid].codec == "AAC"){//only in case of aac we have more packets, for now + while (nxtBuf.len){ + pesBuffer pesBuf; + pesBuf.tid = nxtBuf.tid; + pesBuf.time = nxtBuf.time + ((double)((nxtBuf.curSampleCount - 1024) * 1000)/ myMeta.tracks[nxtBuf.tid].rate) ; + pesBuf.offset = nxtBuf.offset; + pesBuf.len = nxtBuf.len; + pesBuf.lastPos = nxtBuf.lastPos; + pesBuf.isKey = false; + pesBuf.data = nxtBuf.data.substr(0, nxtBuf.len); + playbackBuf.insert(pesBuf); + + nxtBuf.data.erase(0, nxtBuf.len); + parsePESPayload(thisBuf.tid, nxtBuf); + } + }else{ + nxtBuf.data = nxtBuf.data.substr(0, nxtBuf.len); + playbackBuf.insert(nxtBuf); + } + } + + thisPack.null(); + thisPack["data"] = thisBuf.data; + thisPack["trackid"] = thisBuf.tid; + thisPack["bpos"] = thisBuf.bpos; + thisPack["time"] = thisBuf.time; + if (thisBuf.offset){ + thisPack["offset"] = thisBuf.offset; + } + if (thisBuf.isKey){ + thisPack["keyframe"] = 1LL; + } + std::string tmpStr = thisPack.toNetPacked(); + thisPacket.reInit(tmpStr.data(), tmpStr.size()); + } + + ///Seeks to a specific time + void inputTS::seek(int seekTime){ + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + if (feof(inFile)){ + clearerr(inFile); + fseek(inFile, 0, SEEK_SET); + } + pesBuffer tmpBuf; + tmpBuf.tid = *it; + for (unsigned int i = 0; i < myMeta.tracks[*it].keys.size(); i++){ + if (myMeta.tracks[*it].keys[i].getTime() > seekTime){ + break; + } + if (myMeta.tracks[*it].keys[i].getTime() > tmpBuf.time){ + tmpBuf.time = myMeta.tracks[*it].keys[i].getTime(); + tmpBuf.bpos = myMeta.tracks[*it].keys[i].getBpos(); + } + } + + bool foundPacket = false; + unsigned long long lastPos; + pesBuffer nxtBuf; + while ( !foundPacket){ + lastPos = ftell(inFile); + if (feof(inFile)){ + DEBUG_MSG(DLVL_WARN, "Reached EOF during seek to %u in track %d - aborting @ %lld", seekTime, *it, lastPos); + return; + } + fseek(inFile, tmpBuf.bpos, SEEK_SET); + nxtBuf = readFullPES(*it); + if (nxtBuf.time >= seekTime){ + foundPacket = true; + }else{ + tmpBuf.bpos = nxtBuf.lastPos; + } + } + if (myMeta.tracks[nxtBuf.tid].codec == "AAC"){//only in case of aac we have more packets, for now + while (nxtBuf.len){ + pesBuffer pesBuf; + pesBuf.tid = nxtBuf.tid; + pesBuf.time = nxtBuf.time + ((double)((nxtBuf.curSampleCount - 1024) * 1000)/ myMeta.tracks[nxtBuf.tid].rate); + pesBuf.offset = nxtBuf.offset; + pesBuf.len = nxtBuf.len; + pesBuf.lastPos = nxtBuf.lastPos; + pesBuf.isKey = false; + pesBuf.data = nxtBuf.data.substr(0, nxtBuf.len); + playbackBuf.insert(pesBuf); + + nxtBuf.data.erase(0, nxtBuf.len); + parsePESPayload(nxtBuf.tid, nxtBuf); + } + }else{ + playbackBuf.insert(nxtBuf); + } + } + } +} diff --git a/src/input/input_ts.h b/src/input/input_ts.h new file mode 100755 index 00000000..c02f4d0e --- /dev/null +++ b/src/input/input_ts.h @@ -0,0 +1,81 @@ +#include "input.h" +#include +#include +#include +#include +#include + + +namespace Mist { + class pesBuffer { + public: + pesBuffer() : lastPos(0), len(0), time(0), offset(0), bpos(0), curSampleCount(0), isKey(false) {} + ///\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 pesBuffer & rhs) const { + if (time < rhs.time) { + return true; + } else { + if (time == rhs.time){ + if (tid < rhs.tid){ + return true; + } + } + } + return false; + } + int tid;//When used for buffering, not for header generation + long long int lastPos;//set by readFullPES, stores the byteposition directly after the last read ts packet + long long int len; + std::string data; + long long int time; + long long int offset; + long long int bpos; + long long int curSampleCount; + bool isKey; + std::string sps; + std::string pps; + }; + + +/* + /// This struct stores all metadata of a track, and sends them once a whole PES has been analyzed and sent + struct trackInfo{ + //saves all data that needs to be sent. + //as packets can be interleaved, the data needs to be temporarily stored + long long int lastPos;//last byte position of trackSelect + long long int pesTime;//the pes time of the current pes packet + bool keyframe;//whether the current pes packet of the track has a keyframe or not + std::string curPayload;//payload to be sent to user + unsigned int packetCount;//number of TS packets read between between and end (to set bpos correctly) + }; + +*/ + + /// This class contains all functions needed to implement TS Input + class inputTS : public Input { + public: + inputTS(Util::Config * cfg); + protected: + //Private Functions + bool setup(); + bool readHeader(); + void getNext(bool smart = true); + void seek(int seekTime); + void trackSelect(std::string trackSpec); + void parsePESHeader(int tid, pesBuffer & buf); + void parsePESPayload(int tid, pesBuffer & buf); + void parseH264PES(int tid, pesBuffer & buf); + void parseAACPES(int tid, pesBuffer & buf); + pesBuffer readFullPES(int tid); + FILE * inFile;/// playbackBuf;///Used for buffering playback items + std::map firstTimes; + }; +} + +typedef Mist::inputTS mistIn; + diff --git a/src/input/mist_in_folder.cpp b/src/input/mist_in_folder.cpp new file mode 100644 index 00000000..42e84bab --- /dev/null +++ b/src/input/mist_in_folder.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include +#include + +#include INPUTTYPE +#include +#include +#include + +int main(int argc, char * argv[]) { + Util::Config conf(argv[0], PACKAGE_VERSION); + mistIn conv(&conf); + if (conf.parseArgs(argc, argv)) { + if (conf.getBool("json")) { + conv.run(); + return 0; + } + + std::string strm = conf.getString("streamname"); + if (strm.find_first_of("+ ") == std::string::npos){ + FAIL_MSG("Folder input requires a + or space in the stream name."); + return 1; + } + + std::string folder = conf.getString("input"); + if (folder[folder.size() - 1] != '/'){ + FAIL_MSG("Input path must end in a forward slash."); + return 1; + } + std::string folder_noslash = folder.substr(0, folder.size() - 1); + struct stat fileCheck; + if (stat(folder_noslash.c_str(), &fileCheck) != 0 || !S_ISDIR(fileCheck.st_mode)){ + FAIL_MSG("Folder input requires a folder as input."); + return 1; + } + + std::string path = folder + strm.substr(strm.find_first_of("+ ")+1); + if (stat(path.c_str(), &fileCheck) != 0 || S_ISDIR(fileCheck.st_mode)){ + FAIL_MSG("File not found: %s", path.c_str()); + return 1; + } + + Util::startInput(strm, path, false); + return 1; + } + return 1; +} + + diff --git a/src/output/mist_out.cpp b/src/output/mist_out.cpp index 127e4e87..83315c71 100644 --- a/src/output/mist_out.cpp +++ b/src/output/mist_out.cpp @@ -1,6 +1,14 @@ #include OUTPUTTYPE #include #include +#include + +/*LTS-START*/ +#ifdef GEOIP +#define GEOIPV4 "GeoIP.dat" +#define GEOIPV6 "GeoIPv6.dat" +#endif +/*LTS-END*/ int spawnForked(Socket::Connection & S){ mistOut tmp(S); @@ -10,6 +18,21 @@ int spawnForked(Socket::Connection & S){ int main(int argc, char * argv[]) { Util::Config conf(argv[0], PACKAGE_VERSION); mistOut::init(&conf); + /*LTS-START*/ + #ifdef GEOIP + mistOut::geoIP4 = GeoIP_open("/usr/share/GeoIP/" GEOIPV4, GEOIP_STANDARD | GEOIP_CHECK_CACHE); + if (!mistOut::geoIP4){ + mistOut::geoIP4 = GeoIP_open(GEOIPV4, GEOIP_STANDARD | GEOIP_CHECK_CACHE); + } + mistOut::geoIP6 = GeoIP_open("/usr/share/GeoIP/" GEOIPV6, GEOIP_STANDARD | GEOIP_CHECK_CACHE); + if (!mistOut::geoIP6){ + mistOut::geoIP6 = GeoIP_open(GEOIPV6, GEOIP_STANDARD | GEOIP_CHECK_CACHE); + } + if (!mistOut::geoIP4 || !mistOut::geoIP6){ + DEBUG_MSG(DLVL_FAIL, "Could not load all GeoIP databases. %s: %s, %s: %s", GEOIPV4, mistOut::geoIP4?"success":"fail", GEOIPV6, mistOut::geoIP6?"success":"fail"); + } + #endif + /*LTS-END*/ if (conf.parseArgs(argc, argv)) { if (conf.getBool("json")) { std::cout << mistOut::capa.toString() << std::endl; @@ -23,5 +46,11 @@ int main(int argc, char * argv[]) { return tmp.run(); } } + /*LTS-START*/ + #ifdef GEOIP + GeoIP_delete(mistOut::geoIP4); + GeoIP_delete(mistOut::geoIP6); + #endif + /*LTS-END*/ return 0; } diff --git a/src/output/output.cpp b/src/output/output.cpp index a45a2733..edd245e2 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -12,6 +12,12 @@ #include #include "output.h" +/*LTS-START*/ +#include +#include +#include +/*LTS-END*/ + namespace Mist { JSON::Value Output::capa = JSON::Value(); @@ -29,6 +35,11 @@ namespace Mist { capa["optional"]["debug"]["help"] = "The debug level at which messages need to be printed."; capa["optional"]["debug"]["option"] = "--debug"; capa["optional"]["debug"]["type"] = "debug"; + capa["optional"]["startpos"]["name"] = "Starting position in live buffer"; + capa["optional"]["startpos"]["help"] = "For live, where in the buffer the stream starts playback by default. 0 = beginning, 1000 = end"; + capa["optional"]["startpos"]["option"] = "--startPos"; + capa["optional"]["startpos"]["type"] = "uint"; + cfg->addOption("startpos", JSON::fromString("{\"arg\":\"uint\",\"default\":500,\"short\":\"P\",\"long\":\"startPos\",\"help\":\"For live, where in the buffer the stream starts playback by default. 0 = beginning, 1000 = end\"}")); } Output::Output(Socket::Connection & conn) : myConn(conn) { @@ -377,6 +388,273 @@ namespace Mist { return false; } } + + /*LTS-START*/ + bool Output::onList(std::string ip, std::string list){ + if (list == ""){ + return false; + } + std::string entry; + std::string lowerIpv6;//lower-case + std::string upperIpv6;//full-caps + do{ + entry = list.substr(0,list.find(" "));//make sure we have a single entry + lowerIpv6 = "::ffff:" + entry; + upperIpv6 = "::FFFF:" + entry; + if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){ + return true; + } + long long unsigned int starPos = entry.find("*"); + if (starPos == std::string::npos){ + if (ip == entry){ + return true; + } + }else{ + if (starPos == 0){//beginning of the filter + if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){ + return true; + } + }else{ + if (starPos == entry.size() - 1){//end of the filter + if (ip.find(entry.substr(0, entry.size() - 1)) == 0 ){ + return true; + } + if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0 ){ + return true; + } + if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0 ){ + return true; + } + }else{ + Log("CONF","Invalid list entry detected: " + entry); + } + } + } + list.erase(0, entry.size() + 1); + }while (list != ""); + return false; + } + + void Output::Log(std::string type, std::string message){ + /// \todo These logs need to show up in the controller. + /// \todo Additionally, the triggering and untriggering of limits should be recorded in the controller as well. + if (type == "HLIM"){ + DEBUG_MSG(DLVL_HIGH, "HardLimit Triggered: %s", message.c_str()); + } + if (type == "SLIM"){ + DEBUG_MSG(DLVL_HIGH, "SoftLimit Triggered: %s", message.c_str()); + } + } + + std::string Output::hostLookup(std::string ip){ + struct sockaddr_in6 sa; + char hostName[1024]; + char service[20]; + if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){ + return "\n"; + } + sa.sin6_family = AF_INET6; + sa.sin6_port = 0; + sa.sin6_flowinfo = 0; + sa.sin6_scope_id = 0; + int tmpRet = getnameinfo((struct sockaddr*)&sa, sizeof sa, hostName, sizeof hostName, service, sizeof service, NI_NAMEREQD ); + if ( tmpRet == 0){ + return hostName; + } + return ""; + } + + bool Output::isBlacklisted(std::string host, std::string streamName, int timeConnected){ + return false;//blacklisting temporarily disabled for performance reasons + JSON::Value Storage = JSON::fromFile(Util::getTmpFolder() + "streamlist"); + std::string myHostName = hostLookup(host); + if (myHostName == "\n"){ + return false; + } + std::string myCountryName = getCountry(host); + JSON::ArrIter limitIt; + bool hasWhitelist = false; + bool hostOnWhitelist = false; + if (Storage["streams"].isMember(streamName)){ + if (Storage["streams"][streamName].isMember("limits") && Storage["streams"][streamName]["limits"].size()){ + for (limitIt = Storage["streams"][streamName]["limits"].ArrBegin(); limitIt != Storage["streams"][streamName]["limits"].ArrEnd(); limitIt++){ + if ((*limitIt)["name"].asString() == "host"){ + if ((*limitIt)["value"].asString()[0] == '+'){ + if (!onList(host, (*limitIt)["value"].asString().substr(1))){ + if (myHostName == ""){ + if (timeConnected > Storage["config"]["limit_timeout"].asInt()){ + return true; + } + }else{ + if ( !onList(myHostName, (*limitIt)["value"].asStringRef().substr(1))){ + if ((*limitIt)["type"].asStringRef() == "hard"){ + Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName); + } + } + } + } + }else{ + if ((*limitIt)["value"].asStringRef().size() > 1 && (*limitIt)["value"].asStringRef()[0] == '-'){ + if (onList(host, (*limitIt)["value"].asStringRef().substr(1))){ + if ((*limitIt)["type"].asStringRef() == "hard"){ + Log("HLIM", "Host " + host + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " blacklisted for stream " + streamName); + } + } + if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asStringRef() == "hard"){ + Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName); + } + } + } + } + } + if ((*limitIt)["name"].asString() == "geo"){ + if ((*limitIt)["value"].asString()[0] == '+'){ + if (myCountryName == ""){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName); + } + } + if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName); + } + } + }else{ + if ((*limitIt)["value"].asString()[0] == '-'){ + if (onList(myCountryName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName); + } + } + } + } + } + } + } + } + if (Storage["config"]["limits"].size()){ + for (limitIt = Storage["config"]["limits"].ArrBegin(); limitIt != Storage["config"]["limits"].ArrEnd(); limitIt++){ + if ((*limitIt)["name"].asString() == "host"){ + if ((*limitIt)["value"].asString()[0] == '+'){ + if (!onList(host, (*limitIt)["value"].asString().substr(1))){ + if (myHostName == ""){ + if (timeConnected > Storage["config"]["limit_timeout"].asInt()){ + return true; + } + }else{ + if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName); + } + } + } + } + }else{ + if ((*limitIt)["value"].asString()[0] == '-'){ + if (onList(host, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " blacklisted for stream " + streamName); + } + } + if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName); + } + } + } + } + } + if ((*limitIt)["name"].asString() == "geo"){ + if ((*limitIt)["value"].asString()[0] == '+'){ + if (myCountryName == ""){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName); + } + } + if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName); + } + } + }else{ + if ((*limitIt)["value"].asStringRef().size() > 1 && (*limitIt)["value"].asStringRef()[0] == '-'){ + if (onList(myCountryName, (*limitIt)["value"].asStringRef().substr(1))){ + if ((*limitIt)["type"].asString() == "hard"){ + Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName); + return true; + }else{ + Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName); + } + } + } + } + } + } + } + if (hasWhitelist){ + if (hostOnWhitelist || myHostName == ""){ + return false; + }else{ + return true; + } + } + return false; + } + + #ifdef GEOIP + GeoIP * Output::geoIP4 = 0; + GeoIP * Output::geoIP6 = 0; + #endif + std::string Output::getCountry(std::string ip){ + char * code = NULL; + #ifdef GEOIP + if (geoIP4){ + code = (char*)GeoIP_country_code_by_addr(geoIP4, ip.c_str()); + } + if (!code && geoIP6){ + code = (char*)GeoIP_country_code_by_addr_v6(geoIP6, ip.c_str()); + } + #endif + if (!code){ + return ""; + } + return code; + } + /*LTS-END*/ void Output::requestHandler(){ static bool firstData = true;//only the first time, we call onRequest if there's data buffered already. @@ -586,6 +864,12 @@ namespace Mist { if (statsPage.getData()){ unsigned long long int now = Util::epoch(); if (now != lastStats){ + /*LTS-START*/ + if (statsPage.getData()[-1] > 127){ + myConn.close(); + return; + } + /*LTS-END*/ lastStats = now; IPC::statExchange tmpEx(statsPage.getData()); tmpEx.now(now); diff --git a/src/output/output.h b/src/output/output.h index 922a0c9a..255d0f3c 100644 --- a/src/output/output.h +++ b/src/output/output.h @@ -9,15 +9,20 @@ #include #include #include +/*LTS-START*/ +#ifdef GEOIP +#include +#endif +/*LTS-END*/ #include "../io.h" namespace Mist { - + /// This struct keeps packet information sorted in playback order, so the /// Mist::Output class knows when to buffer which packet. - struct sortedPageInfo{ + struct sortedPageInfo { bool operator < (const sortedPageInfo & rhs) const { - if (time < rhs.time){ + if (time < rhs.time) { return true; } return (time == rhs.time && tid < rhs.tid); @@ -41,6 +46,12 @@ namespace Mist { //static members for initialization and capabilities static void init(Util::Config * cfg); static JSON::Value capa; + /*LTS-START*/ + #ifdef GEOIP + static GeoIP * geoIP4; + static GeoIP * geoIP6; + #endif + /*LTS-END*/ //non-virtual generic functions int run(); void stats(); @@ -64,6 +75,14 @@ namespace Mist { virtual void onFail(); virtual void requestHandler(); private://these *should* not be messed with in child classes. + /*LTS-START*/ + void Log(std::string type, std::string message); + bool checkLimits(); + bool isBlacklisted(std::string host, std::string streamName, int timeConnected); + std::string hostLookup(std::string ip); + bool onList(std::string ip, std::string list); + std::string getCountry(std::string ip); + /*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); @@ -82,7 +101,7 @@ namespace Mist { unsigned int maxSkipAhead;///< Maximum ms that we will go ahead of the intended timestamps. unsigned int minSkipAhead;///< Minimum ms that we will go ahead of the intended timestamps. unsigned int realTime;///< Playback speed times 1000 (1000 == 1.0X). Zero is infinite. - + //Read/write status variables Socket::Connection & myConn;///< Connection to the client. @@ -97,3 +116,4 @@ namespace Mist { }; } + diff --git a/src/output/output_dash_mp4.cpp b/src/output/output_dash_mp4.cpp new file mode 100644 index 00000000..fa40e49e --- /dev/null +++ b/src/output/output_dash_mp4.cpp @@ -0,0 +1,669 @@ +#include "output_dash_mp4.h" +#include +#include +#include +#include +#include + +namespace Mist { + OutDashMP4::OutDashMP4(Socket::Connection & conn) : HTTPOutput(conn){realTime = 0;} + OutDashMP4::~OutDashMP4(){} + + std::string OutDashMP4::makeTime(long long unsigned int time){ + std::stringstream r; + r << "PT" << (((time / 1000) / 60) /60) << "H" << ((time / 1000) / 60) % 60 << "M" << (time / 1000) % 60 << "." << time % 1000 / 10 << "S"; + return r.str(); + } + + void OutDashMP4::buildFtyp(unsigned int tid){ + H.Chunkify("\000\000\000", 3, myConn); + H.Chunkify("\040", 1, myConn); + H.Chunkify("ftypisom\000\000\000\000isom", 16, myConn); + if (myMeta.tracks[tid].type == "video"){ + H.Chunkify("avc1", 4, myConn); + }else{ + H.Chunkify("M4A ", 4, myConn); + } + H.Chunkify("mp42dash", 8, myConn); + } + + void OutDashMP4::buildStyp(unsigned int tid){ + H.Chunkify("\000\000\000\030stypmsdh\000\000\000\000msdhmsix", 24, myConn); + } + + std::string OutDashMP4::buildMoov(unsigned int tid){ + std::string trackType = myMeta.tracks[tid].type; + MP4::MOOV moovBox; + + MP4::MVHD mvhdBox(0); + mvhdBox.setTrackID(2); + mvhdBox.setDuration(0xFFFFFFFF); + moovBox.setContent(mvhdBox, 0); + + MP4::IODS iodsBox; + if (trackType == "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, myMeta.tracks[tid].width, myMeta.tracks[tid].height); + tkhdBox.setFlags(3); + if (trackType == "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(myMeta.tracks[tid].lastms); + mdiaBox.setContent(mdhdBox, 0); + + if (trackType == "video"){ + MP4::HDLR hdlrBox(myMeta.tracks[tid].type,"VideoHandler"); + mdiaBox.setContent(hdlrBox, 1); + }else{ + MP4::HDLR hdlrBox(myMeta.tracks[tid].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 (myMeta.tracks[tid].codec == "H264"){ + MP4::AVC1 avc1Box; + avc1Box.setWidth(myMeta.tracks[tid].width); + avc1Box.setHeight(myMeta.tracks[tid].height); + + MP4::AVCC avccBox; + avccBox.setPayload(myMeta.tracks[tid].init); + avc1Box.setCLAP(avccBox); + stsdBox.setEntry(avc1Box, 0); + } + if (myMeta.tracks[tid].codec == "HEVC"){ + MP4::HEV1 hev1Box; + hev1Box.setWidth(myMeta.tracks[tid].width); + hev1Box.setHeight(myMeta.tracks[tid].height); + + MP4::HVCC hvccBox; + hvccBox.setPayload(myMeta.tracks[tid].init); + hev1Box.setCLAP(hvccBox); + stsdBox.setEntry(hev1Box, 0); + } + if (myMeta.tracks[tid].codec == "AAC"){ + MP4::AudioSampleEntry ase; + ase.setCodec("mp4a"); + ase.setDataReferenceIndex(1); + ase.setSampleRate(myMeta.tracks[tid].rate); + ase.setChannelCount(myMeta.tracks[tid].channels); + ase.setSampleSize(myMeta.tracks[tid].size); + MP4::ESDS esdsBox(myMeta.tracks[tid].init); + ase.setCodecBox(esdsBox); + stsdBox.setEntry(ase,0); + } + if (myMeta.tracks[tid].codec == "AC3"){ + ///\todo Note: this code is copied, note for muxing seperation + MP4::AudioSampleEntry ase; + ase.setCodec("ac-3"); + ase.setDataReferenceIndex(1); + ase.setSampleRate(myMeta.tracks[tid].rate); + ase.setChannelCount(myMeta.tracks[tid].channels); + ase.setSampleSize(myMeta.tracks[tid].size); + MP4::DAC3 dac3Box; + switch (myMeta.tracks[tid].rate){ + case 48000: + dac3Box.setSampleRateCode(0); + break; + case 44100: + dac3Box.setSampleRateCode(1); + break; + case 32000: + dac3Box.setSampleRateCode(2); + break; + default: + dac3Box.setSampleRateCode(3); + break; + } + /// \todo the next settings are set to generic values, we might want to make these flexible + dac3Box.setBitStreamIdentification(8);//check the docs, this is a weird property + dac3Box.setBitStreamMode(0);//set to main, mixed audio + dac3Box.setAudioConfigMode(2);///\todo find out if ACMode should be different + if (myMeta.tracks[tid].channels > 4){ + dac3Box.setLowFrequencyEffectsChannelOn(1); + }else{ + dac3Box.setLowFrequencyEffectsChannelOn(0); + } + dac3Box.setFrameSizeCode(20);//should be OK, but test this. + ase.setCodecBox(dac3Box); + } + + 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 (trackType == "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); + + return std::string(moovBox.asBox(),moovBox.boxedSize()); + } + + std::string OutDashMP4::buildSidx(unsigned int tid){ + MP4::AVCC avccBox; + MP4::HVCC hvccBox; + if (myMeta.tracks[tid].codec == "H264"){ + avccBox.setPayload(myMeta.tracks[tid].init); + } + if (myMeta.tracks[tid].codec == "HEVC"){ + hvccBox.setPayload(myMeta.tracks[tid].init); + } + int curPart = 0; + MP4::SIDX sidxBox; + sidxBox.setReferenceID(1); + sidxBox.setTimescale(1000); + sidxBox.setEarliestPresentationTime(myMeta.tracks[tid].firstms); + sidxBox.setFirstOffset(0); + int j = 0; + for (std::deque::iterator it = myMeta.tracks[tid].keys.begin(); it != myMeta.tracks[tid].keys.end(); it++){ + MP4::sidxReference refItem; + refItem.referenceType = false; + refItem.referencedSize = 0; + for (int i = 0; i < it->getParts(); i++){ + refItem.referencedSize += myMeta.tracks[tid].parts[curPart++].getSize(); + } + if (myMeta.tracks[tid].codec == "H264"){ + refItem.referencedSize += 14 + avccBox.getSPSLen() + avccBox.getPPSLen(); + } + if (myMeta.tracks[tid].codec == "HEVC"){ + 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++){ + refItem.referencedSize += 4 + (*it2).size(); + } + } + } + fragmentSizes[tid][j] = refItem.referencedSize; + if (it->getLength()){ + refItem.subSegmentDuration = it->getLength(); + }else{ + refItem.subSegmentDuration = myMeta.tracks[tid].lastms - it->getTime(); + } + refItem.sapStart = false; + refItem.sapType = 0; + refItem.sapDeltaTime = 0; + sidxBox.setReference(refItem, j++); + } + return std::string(sidxBox.asBox(),sidxBox.boxedSize()); + } + + std::string OutDashMP4::buildSidx(unsigned int tid, unsigned int keyNum){ + MP4::AVCC avccBox; + avccBox.setPayload(myMeta.tracks[tid].init); + int curPart = 0; + MP4::SIDX sidxBox; + sidxBox.setReferenceID(1); + sidxBox.setTimescale(1000); + sidxBox.setEarliestPresentationTime(myMeta.tracks[tid].keys[keyNum].getTime()); + sidxBox.setFirstOffset(0); + for (int i = 0; i < keyNum; i++){ + curPart += myMeta.tracks[tid].keys[i].getParts(); + } + MP4::sidxReference refItem; + refItem.referenceType = false; + if (myMeta.tracks[tid].keys[keyNum].getLength()){ + refItem.subSegmentDuration = myMeta.tracks[tid].keys[keyNum].getLength(); + }else{ + refItem.subSegmentDuration = myMeta.tracks[tid].lastms - myMeta.tracks[tid].keys[keyNum].getTime(); + } + refItem.sapStart = false; + refItem.sapType = 0; + refItem.sapDeltaTime = 0; + sidxBox.setReference(refItem, 0); + return std::string(sidxBox.asBox(),sidxBox.boxedSize()); + } + + std::string OutDashMP4::buildMoof(unsigned int tid, unsigned int keyNum){ + MP4::MOOF moofBox; + + MP4::MFHD mfhdBox; + mfhdBox.setSequenceNumber(keyNum + 1); + moofBox.setContent(mfhdBox, 0); + + MP4::TRAF trafBox; + MP4::TFHD tfhdBox; + if (myMeta.tracks[tid].codec == "H264" || myMeta.tracks[tid].codec == "HEVC"){ + tfhdBox.setTrackID(1); + } + if (myMeta.tracks[tid].codec == "AAC"){ + tfhdBox.setFlags(MP4::tfhdSampleFlag); + tfhdBox.setTrackID(1); + tfhdBox.setDefaultSampleFlags(MP4::isKeySample); + } + trafBox.setContent(tfhdBox, 0); + + MP4::TFDT tfdtBox; + ///\todo Determine index for live + tfdtBox.setBaseMediaDecodeTime(myMeta.tracks[tid].keys[keyNum].getTime()); + trafBox.setContent(tfdtBox, 1); + + int i = 0; + + for (int j = 0; j < keyNum; j++){ + i += myMeta.tracks[tid].keys[j].getParts(); + } + + MP4::TRUN trunBox; + if (myMeta.tracks[tid].codec == "H264"){ + trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration | MP4::trunfirstSampleFlags | MP4::trunsampleOffsets); + trunBox.setFirstSampleFlags(MP4::isKeySample); + trunBox.setDataOffset(88 + (12 * myMeta.tracks[tid].keys[keyNum].getParts()) + 8); + + MP4::AVCC avccBox; + avccBox.setPayload(myMeta.tracks[tid].init); + for (int j = 0; j < myMeta.tracks[tid].keys[keyNum].getParts(); j++){ + MP4::trunSampleInformation trunEntry; + if (!j){ + trunEntry.sampleSize = myMeta.tracks[tid].parts[i].getSize() + 14 + avccBox.getSPSLen() + avccBox.getPPSLen(); + }else{ + trunEntry.sampleSize = myMeta.tracks[tid].parts[i].getSize(); + } + trunEntry.sampleDuration = myMeta.tracks[tid].parts[i].getDuration(); + trunEntry.sampleOffset = myMeta.tracks[tid].parts[i].getOffset(); + trunBox.setSampleInformation(trunEntry, j); + i++; + } + } + if (myMeta.tracks[tid].codec == "HEVC"){ + trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration | MP4::trunfirstSampleFlags | MP4::trunsampleOffsets); + trunBox.setFirstSampleFlags(MP4::isKeySample); + trunBox.setDataOffset(88 + (12 * myMeta.tracks[tid].keys[keyNum].getParts()) + 8); + + MP4::HVCC hvccBox; + hvccBox.setPayload(myMeta.tracks[tid].init); + std::deque content = hvccBox.getArrays(); + for (int j = 0; j < myMeta.tracks[tid].keys[keyNum].getParts(); j++){ + MP4::trunSampleInformation trunEntry; + trunEntry.sampleSize = myMeta.tracks[tid].parts[i].getSize(); + if (!j){ + for (std::deque::iterator it = content.begin(); it != content.end(); it++){ + for (std::deque::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){ + trunEntry.sampleSize += 4 + (*it2).size(); + } + } + } + trunEntry.sampleDuration = myMeta.tracks[tid].parts[i].getDuration(); + trunEntry.sampleOffset = myMeta.tracks[tid].parts[i].getOffset(); + trunBox.setSampleInformation(trunEntry, j); + i++; + } + } + if (myMeta.tracks[tid].codec == "AAC"){ + trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration); + trunBox.setDataOffset(88 + (8 * myMeta.tracks[tid].keys[keyNum].getParts()) + 8); + for (int j = 0; j < myMeta.tracks[tid].keys[keyNum].getParts(); j++){ + MP4::trunSampleInformation trunEntry; + trunEntry.sampleSize = myMeta.tracks[tid].parts[i].getSize(); + trunEntry.sampleDuration = myMeta.tracks[tid].parts[i].getDuration(); + trunBox.setSampleInformation(trunEntry, j); + i++; + } + } + trafBox.setContent(trunBox, 2); + + moofBox.setContent(trafBox, 1); + + return std::string(moofBox.asBox(), moofBox.boxedSize()); + } + + 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::buildMdat(unsigned int tid, unsigned int keyNum){ + MP4::AVCC avccBox; + avccBox.setPayload(myMeta.tracks[tid].init); + std::stringstream r; + int size = fragmentSizes[tid][keyNum] + 8; + r << (char)((size >> 24) & 0xFF); + r << (char)((size >> 16) & 0xFF); + r << (char)((size >> 8) & 0xFF); + r << (char)((size) & 0xFF); + r << "mdat"; + H.Chunkify(r.str().data(), r.str().size(), myConn); + selectedTracks.clear(); + selectedTracks.insert(tid); + seek(myMeta.tracks[tid].keys[keyNum].getTime()); + std::string init; + char * data; + unsigned int dataLen; + int partNum = 0; + for (int i = 0; i < keyNum; i++){ + partNum += myMeta.tracks[tid].keys[i].getParts(); + } + if (myMeta.tracks[tid].codec == "H264"){ + 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 (myMeta.tracks[tid].codec == "HEVC"){ + MP4::HVCC hvccBox; + hvccBox.setPayload(myMeta.tracks[tid].init); + std::deque content = hvccBox.getArrays(); + for (int j = 0; j < myMeta.tracks[tid].keys[keyNum].getParts(); j++){ + 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); + } + } + } + } + for (int i = 0; i < myMeta.tracks[tid].keys[keyNum].getParts(); i++){ + prepareNext(); + thisPacket.getString("data", data, dataLen); + H.Chunkify(data, dataLen, myConn); + } + return; + } + + std::string OutDashMP4::buildManifest(){ + initialize(); + int lastTime = 0; + int lastVidTime = 0; + int vidKeys = 0; + int vidInitTrack = 0; + int lastAudTime = 0; + int audKeys = 0; + int audInitTrack = 0; + for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it ++){ + if (it->second.lastms > lastTime){ + lastTime = it->second.lastms; + } + if (it->second.codec == "H264" && it->second.lastms > lastVidTime){ + lastVidTime = it->second.lastms; + vidKeys = it->second.keys.size(); + vidInitTrack = it->first; + } + if (it->second.codec == "HEVC" && it->second.lastms > lastVidTime){ + lastVidTime = it->second.lastms; + vidKeys = it->second.keys.size(); + vidInitTrack = it->first; + } + if (it->second.codec == "AAC" && it->second.lastms > lastAudTime){ + lastAudTime = it->second.lastms; + audKeys = it->second.keys.size(); + audInitTrack = it->first; + } + } + std::stringstream r; + r << "" << std::endl; + r << "" << std::endl; + r << " " << streamName << "" << std::endl; + r << " " << std::endl; + if (vidInitTrack){ + r << " " << std::endl; + r << " " << std::endl; + r << " " << std::endl; + for (int i = 0; i < myMeta.tracks[vidInitTrack].keys.size() - 1; i++){ + r << " " << std::endl; + } + int lastDur = myMeta.tracks[vidInitTrack].lastms - myMeta.tracks[vidInitTrack].keys.rbegin()->getTime(); + r << " " << std::endl; + r << " " << std::endl; + r << " " << std::endl; + for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ + if (it->second.codec == "H264"){ + MP4::AVCC avccBox; + avccBox.setPayload(it->second.init); + r << " first << "\" "; + r << "codecs=\"avc1."; + r << std::hex << std::setw(2) << std::setfill('0') << (int)avccBox.getSPS()[0] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)avccBox.getSPS()[1] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)avccBox.getSPS()[2] << std::dec; + r << "\" "; + r << "bandwidth=\"" << it->second.bps << "\" "; + r << "/>" << std::endl; + } + if (it->second.codec == "HEVC"){ + r << " first << "\" "; + r << "codecs=\"hev1."; + r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[1] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[6] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[7] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[8] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[9] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[10] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[11] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[12] << std::dec; + r << "\" "; + r << "bandwidth=\"" << it->second.bps << "\" "; + r << "/>" << std::endl; + } + } + r << " " << std::endl; + } + if (audInitTrack){ + r << " " << std::endl; + r << " " << std::endl; + r << " " << std::endl; + + r << " " << std::endl; + for (int i = 0; i < myMeta.tracks[audInitTrack].keys.size() - 1; i++){ + r << " " << std::endl; + } + int lastDur = myMeta.tracks[audInitTrack].lastms - myMeta.tracks[audInitTrack].keys.rbegin()->getTime(); + r << " " << std::endl; + r << " " << std::endl; + r << " " << std::endl; + + for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ + if (it->second.codec == "AAC"){ + r << " first << "\" "; + r << "codecs=\"mp4a.40.2\" "; + r << "audioSamplingRate=\"" << it->second.rate << "\" "; + r << "bandwidth=\"" << it->second.bps << "\">" << std::endl; + r << " second.channels << "\" />" << std::endl; + r << " " << std::endl; + } + } + 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["desc"] = "Enables HTTP protocol progressive streaming."; + capa["url_rel"] = "/dash/$/index.mpd"; + capa["url_prefix"] = "/dash/$/"; + capa["socket"] = "http_dash_mp4"; + capa["codecs"][0u][0u].append("H264"); + capa["codecs"][0u][0u].append("HEVC"); + capa["codecs"][0u][1u].append("AAC"); + capa["codecs"][0u][1u].append("AC3"); + capa["methods"][0u]["handler"] = "http"; + capa["methods"][0u]["type"] = "dash/video/mp4"; + capa["methods"][0u]["priority"] = 8ll; + capa["methods"][0u]["nolive"] = 1; + } + + /// Parses a "Range: " header, setting byteStart, byteEnd and seekPoint using data from metadata and tracks to do + /// the calculations. + /// On error, byteEnd is set to zero. + void OutDashMP4::parseRange(std::string header, long long & byteStart, long long & byteEnd){ + int firstPos = header.find("=") + 1; + byteStart = atoll(header.substr(firstPos, header.find("-", firstPos)).c_str()); + byteEnd = atoll(header.substr(header.find("-", firstPos) + 1).c_str()); + + DEBUG_MSG(DLVL_DEVEL, "Range request: %lli-%lli (%s)", byteStart, byteEnd, header.c_str()); + } + + int OutDashMP4::getKeyFromRange(unsigned int tid, long long int byteStart){ + unsigned long long int currOffset = 0; + for (int i = 0; i < myMeta.tracks[tid].keys.size(); i++){ + if (byteStart == currOffset){ + return i; + } + if (byteStart < currOffset && i > 0){ + return i - 1; + } + DEBUG_MSG(DLVL_DEVEL, "%lld > %llu", byteStart, currOffset); + } + return -1; + } + + void OutDashMP4::initialize(){ + HTTPOutput::initialize(); + for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ + if (!moovBoxes.count(it->first)){ + moovBoxes[it->first] = buildMoov(it->first); + buildSidx(it->first); + } + } + } + + void OutDashMP4::onHTTP(){ + initialize(); + std::string url = H.url; + if (H.method == "OPTIONS"){ + H.Clean(); + H.SetHeader("Content-Type", "application/octet-stream"); + H.SetHeader("Cache-Control", "no-cache"); + H.SetHeader("MistMultiplex", "No"); + + H.SetHeader("Access-Control-Allow-Origin", "*"); + H.SetHeader("Access-Control-Allow-Methods", "GET, POST"); + H.SetHeader("Access-Control-Allow-Headers", "Content-Type, X-Requested-With"); + H.SetHeader("Access-Control-Allow-Credentials", "true"); + H.SetBody(""); + H.SendResponse("200", "OK", myConn); + H.Clean(); + return; + } + if (url.find(".mpd") != std::string::npos){ + H.Clean(); + H.SetHeader("Content-Type", "application/xml"); + H.SetHeader("Cache-Control", "no-cache"); + H.SetHeader("MistMultiplex", "No"); + + H.SetHeader("Access-Control-Allow-Origin", "*"); + H.SetHeader("Access-Control-Allow-Methods", "GET, POST"); + H.SetHeader("Access-Control-Allow-Headers", "Content-Type, X-Requested-With"); + H.SetHeader("Access-Control-Allow-Credentials", "true"); + + H.SetBody(buildManifest()); + H.SendResponse("200", "OK", myConn); + DEVEL_MSG("Manifest sent"); + }else{ + long long int bench = Util::getMS(); + int pos = url.find("chunk_") + 6;//put our marker just after the _ beyond chunk + int tid = atoi(url.substr(pos).c_str()); + DEBUG_MSG(DLVL_DEVEL, "Track %d requested", tid); + + H.Clean(); + H.SetHeader("Content-Type", "video/mp4"); + H.SetHeader("Cache-Control", "no-cache"); + H.SetHeader("MistMultiplex", "No"); + + H.SetHeader("Access-Control-Allow-Origin", "*"); + H.SetHeader("Access-Control-Allow-Methods", "GET, POST"); + H.SetHeader("Access-Control-Allow-Headers", "Content-Type, X-Requested-With"); + H.SetHeader("Access-Control-Allow-Credentials", "true"); + H.StartResponse(H, myConn); + + if (url.find("init.m4s") != std::string::npos){ + DEBUG_MSG(DLVL_DEVEL, "Handling init"); + buildFtyp(tid); + H.Chunkify(moovBoxes[tid], myConn); + }else{ + pos = url.find("_", pos + 1) + 1; + int keyId = atoi(url.substr(pos).c_str()); + DEBUG_MSG(DLVL_DEVEL, "Searching for time %d", keyId); + unsigned int keyNum = myMeta.tracks[tid].timeToKeynum(keyId); + INFO_MSG("Detected key %d:%d for time %d", tid, keyNum, keyId); + buildStyp(tid); + std::string tmp = buildSidx(tid, keyNum); + H.Chunkify(tmp, myConn); + tmp = buildMoof(tid, keyNum); + H.Chunkify(tmp, myConn); + buildMdat(tid, keyNum); + } + H.Chunkify("", 0, myConn); + H.Clean(); + INFO_MSG("Done handling request, took %lld ms", Util::getMS() - bench); + return; + } + H.Clean(); + parseData = false; + wantRequest = true; + } + + void OutDashMP4::sendNext(){} + void OutDashMP4::sendHeader(){} +} diff --git a/src/output/output_dash_mp4.h b/src/output/output_dash_mp4.h new file mode 100644 index 00000000..97fbeee4 --- /dev/null +++ b/src/output/output_dash_mp4.h @@ -0,0 +1,33 @@ +#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(); + void initialize(); + protected: + std::string makeTime(long long unsigned int time); + std::string buildManifest(); + void buildFtyp(unsigned int trackid); + void buildStyp(unsigned int trackid); + std::string buildMoov(unsigned int trackid); + std::string buildSidx(unsigned int trackid); + std::string buildSidx(unsigned int trackid, unsigned int keynum); + std::string buildMoof(unsigned int trackid, unsigned int keynum); + void buildMdat(unsigned int trackid, unsigned int keynum); + std::map > fragmentSizes; + std::string buildNalUnit(unsigned int len, const char * data); + void parseRange(std::string header, long long & byteStart, long long & byteEnd); + int getKeyFromRange(unsigned int tid, long long int byteStart); + std::map moovBoxes; + }; +} + +typedef Mist::OutDashMP4 mistOut; diff --git a/src/output/output_hds.cpp b/src/output/output_hds.cpp index f58ee457..7390be4d 100644 --- a/src/output/output_hds.cpp +++ b/src/output/output_hds.cpp @@ -57,6 +57,19 @@ namespace Mist { int j = 0; if (myMeta.tracks[tid].fragments.size()){ std::deque::iterator fragIt = myMeta.tracks[tid].fragments.begin(); + /*LTS-START*/ + if (myMeta.live){ + unsigned int skip = (( myMeta.tracks[tid].fragments.size()-1) * config->getInteger("startpos")) / 1000u; + for (unsigned int z = 0; z < skip; ++z){ + ++fragIt; + ++j; + } + if (skip && fragIt == myMeta.tracks[tid].fragments.end()){ + --fragIt; + --j; + } + } + /*LTS-END*/ unsigned int firstTime = myMeta.tracks[tid].getKey(fragIt->getNumber()).getTime(); while (fragIt != myMeta.tracks[tid].fragments.end()){ if (myMeta.vod || fragIt->getDuration() > 0){ @@ -160,6 +173,7 @@ namespace Mist { capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "flash/11"; capa["methods"][0u]["priority"] = 7ll; + cfg->getOption("startpos", true)[0u] = 0ll; } void OutHDS::sendNext(){ diff --git a/src/output/output_hls.cpp b/src/output/output_hls.cpp index 0b266368..9ae25191 100644 --- a/src/output/output_hls.cpp +++ b/src/output/output_hls.cpp @@ -11,7 +11,7 @@ namespace Mist { int audioId = -1; std::string audioName; for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if (it->second.codec == "AAC"){ + if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3"){ audioId = it->first; audioName = it->second.getIdentifier(); break; @@ -19,7 +19,7 @@ namespace Mist { } unsigned int vidTracks = 0; for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if (it->second.codec == "H264"){ + if (it->second.codec == "H264" || it->second.codec == "HEVC"){ vidTracks++; int bWidth = it->second.bps * 2; if (bWidth < 5){ @@ -84,6 +84,20 @@ namespace Mist { } //only print the last segment when VoD lines.pop_back(); + /*LTS-START*/ + unsigned int skip = (( myMeta.tracks[tid].fragments.size()-1) * config->getInteger("startpos")) / 1000u; + while (skippedLines < skip && lines.size()){ + lines.pop_front(); + skippedLines++; + } + if (config->getInteger("listlimit")){ + unsigned long listlimit = config->getInteger("listlimit"); + while (lines.size() > listlimit){ + lines.pop_front(); + skippedLines++; + } + } + /*LTS-END*/ } result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n"; @@ -112,12 +126,22 @@ namespace Mist { capa["desc"] = "Enables HTTP protocol Apple-specific streaming (also known as HLS)."; capa["url_rel"] = "/hls/$/index.m3u8"; capa["url_prefix"] = "/hls/$/"; + capa["codecs"][0u][0u].append("HEVC"); capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("MP3"); + capa["codecs"][0u][1u].append("AC3"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl"; capa["methods"][0u]["priority"] = 9ll; + /*LTS-START*/ + cfg->addOption("listlimit", JSON::fromString("{\"arg\":\"integer\",\"default\":0,\"short\":\"y\",\"long\":\"list-limit\",\"help\":\"Maximum number of parts in live playlists (0 = infinite).\"}")); + capa["optional"]["listlimit"]["name"] = "Live playlist limit"; + capa["optional"]["listlimit"]["help"] = "Maximum number of parts in live playlists. (0 = infinite)"; + capa["optional"]["listlimit"]["default"] = 0ll; + capa["optional"]["listlimit"]["type"] = "uint"; + capa["optional"]["listlimit"]["option"] = "--list-limit"; + /*LTS-END*/ } int OutHLS::canSeekms(unsigned int ms){ diff --git a/src/output/output_hss.cpp b/src/output/output_hss.cpp index 76036eb3..aad71b9d 100644 --- a/src/output/output_hss.cpp +++ b/src/output/output_hss.cpp @@ -3,6 +3,7 @@ #include #include #include +#include /*LTS*/ #include #include #include @@ -261,7 +262,7 @@ namespace Mist { 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 %ld > %lld", i, 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); @@ -273,6 +274,37 @@ namespace Mist { MP4::MOOF moof_box; moof_box.setContent(mfhd_box, 0); moof_box.setContent(traf_box, 1); + /*LTS-START*/ + if (myMeta.tracks[tid].keys.size() == myMeta.tracks[tid].ivecs.size()) { + std::string tmpVec = std::string(myMeta.tracks[tid].ivecs[keyObj.getNumber() - myMeta.tracks[tid].keys[0].getNumber()].getData(), 8); + unsigned long long int curVec = binToInt(tmpVec); + 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; + newSample.InitializationVector = intToBin(curVec); + curVec++; + sEnc.setSample(newSample, i); + } + } else { + sEnc.setFlags(2); + std::deque tmpParts; + for (int i = 0; i < keyObj.getParts(); i++) { + MP4::UUID_SampleEncryption_Sample newSample; + newSample.InitializationVector = intToBin(curVec); + curVec++; + MP4::UUID_SampleEncryption_Sample_Entry newEntry; + newEntry.BytesClear = 5; + newEntry.BytesEncrypted = myMeta.tracks[tid].parts[partOffset + i].getSize() - 5; + newSample.Entries.push_back(newEntry); + sEnc.setSample(newSample, i); + } + } + traf_box.setContent(sEnc, 3); + } + /*LTS-END*/ //Setting the correct offsets. moof_box.setContent(traf_box, 1); trun_box.setDataOffset(moof_box.boxedSize() + 8); @@ -290,10 +322,36 @@ namespace Mist { H.Clean(); } + /*LTS-START*/ + std::string OutHSS::protectionHeader(JSON::Value & encParams) { + std::string xmlGen = "16AESCTR"; + xmlGen += encParams["keyid"].asString(); + xmlGen += ""; + xmlGen += encParams["la_url"].asString(); + 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 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(){ + /*LTS + std::string smoothIndex(){ + LTS*/ + std::string OutHSS::smoothIndex(JSON::Value encParams) { /*LTS*/ updateMeta(); std::stringstream Result; Result << "\n"; @@ -307,6 +365,7 @@ namespace Mist { long long int maxHeight = 0; long long int minWidth = 99999999; long long int minHeight = 99999999; + bool encrypted = false;/*LTS*/ for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { if (it->second.codec == "AAC") { audioIters.push_back(it); @@ -350,6 +409,7 @@ namespace Mist { "Url=\"Q({bitrate},{CustomAttributes})/A({start time})\">\n"; int index = 0; for (std::deque::iterator>::iterator it = audioIters.begin(); it != audioIters.end(); it++) { + encrypted |= ((*it)->second.keys.size() == (*it)->second.ivecs.size()); /*LTS*/ Result << "second.bps * 8 << "\" " @@ -395,6 +455,7 @@ namespace Mist { "DisplayHeight=\"" << maxHeight << "\">\n"; int index = 0; for (std::deque::iterator>::iterator it = videoIters.begin(); it != videoIters.end(); it++) { + encrypted |= ((*it)->second.keys.size() == (*it)->second.ivecs.size()); /*LTS*/ //Add video qualities Result << "\n"; } + /*LTS-START*/ + if (encrypted) { + Result << ""; + Result << protectionHeader(encParams); + Result << ""; + } + /*LTS-END*/ Result << "\n"; #if DEBUG >= 8 @@ -443,7 +511,10 @@ namespace Mist { H.Clean(); H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Cache-Control", "no-cache"); + /*LTS std::string manifest = smoothIndex(); + LTS*/ + std::string manifest = smoothIndex(encryption);/*LTS*/ H.SetBody(manifest); H.SendResponse("200", "OK", myConn); H.Clean(); @@ -454,8 +525,16 @@ namespace Mist { } } + /*LTS-START*/ void OutHSS::initialize() { Output::initialize(); + JSON::Value servConf = JSON::fromFile(Util::getTmpFolder() + "streamlist"); + encryption["keyseed"] = servConf["streams"][streamName]["keyseed"]; + encryption["keyid"] = servConf["streams"][streamName]["keyid"]; + encryption["contentkey"] = servConf["streams"][streamName]["contentkey"]; + encryption["la_url"] = servConf["streams"][streamName]["la_url"]; + servConf.null(); } + /*LTS-END*/ } diff --git a/src/output/output_hss.h b/src/output/output_hss.h index bc4d6e55..6e0856e6 100644 --- a/src/output/output_hss.h +++ b/src/output/output_hss.h @@ -13,7 +13,11 @@ namespace Mist { void sendHeader(); protected: JSON::Value encryption; + std::string protectionHeader(JSON::Value & encParams);/*LTS*/ + /*LTS std::string smoothIndex(); + LTS*/ + std::string smoothIndex(JSON::Value encParams = JSON::Value());/*LTS*/ int canSeekms(unsigned int ms); int keysToSend; int myTrackStor; diff --git a/src/output/output_http_internal.cpp b/src/output/output_http_internal.cpp index f3932dd8..dfc1eb2b 100644 --- a/src/output/output_http_internal.cpp +++ b/src/output/output_http_internal.cpp @@ -164,6 +164,15 @@ namespace Mist { // send logo icon if (H.url.length() > 4 && H.url.substr(H.url.length() - 4, 4) == ".ico"){ + /*LTS-START*/ + if (H.GetVar("s").size() && H.GetVar("s") == SUPER_SECRET){ + H.Clean(); + H.SetHeader("Server", "mistserver/" PACKAGE_VERSION); + H.SetBody("Yup"); + H.SendResponse("200", "OK", myConn); + return; + } + /*LTS-END*/ H.Clean(); #include "../icon.h" H.SetHeader("Content-Type", "image/x-icon"); @@ -304,6 +313,7 @@ namespace Mist { it->second.removeMember("fragments"); it->second.removeMember("keys"); it->second.removeMember("parts"); + it->second.removeMember("ivecs");/*LTS*/ } //create a set for storing source information diff --git a/src/output/output_httpts.cpp b/src/output/output_httpts.cpp index a391ee48..bfcac9cb 100644 --- a/src/output/output_httpts.cpp +++ b/src/output/output_httpts.cpp @@ -17,8 +17,10 @@ namespace Mist { capa["url_match"] = "/$.ts"; capa["socket"] = "http_ts"; capa["codecs"][0u][0u].append("H264"); + capa["codecs"][0u][0u].append("HEVC"); capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("MP3"); + capa["codecs"][0u][1u].append("AC3"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/video/mp2t"; capa["methods"][0u]["priority"] = 1ll; diff --git a/src/output/output_progressive_mp4.cpp b/src/output/output_progressive_mp4.cpp index ac2964f2..0bc142b7 100644 --- a/src/output/output_progressive_mp4.cpp +++ b/src/output/output_progressive_mp4.cpp @@ -15,8 +15,10 @@ namespace Mist { capa["url_rel"] = "/$.mp4"; capa["url_match"] = "/$.mp4"; capa["codecs"][0u][0u].append("H264"); + capa["codecs"][0u][0u].append("HEVC"); capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("MP3"); + capa["codecs"][0u][1u].append("AC3"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/video/mp4"; capa["methods"][0u]["priority"] = 8ll; @@ -115,6 +117,13 @@ namespace Mist { avccBox.setPayload(thisTrack.init); vse.setCLAP(avccBox); } + /*LTS-START*/ + if (thisTrack.codec == "HEVC"){ + MP4::HVCC hvccBox; + hvccBox.setPayload(thisTrack.init); + vse.setCLAP(hvccBox); + } + /*LTS-END*/ stsdBox.setEntry(vse,0); }else if(thisTrack.type == "audio"){//boxname = codec MP4::AudioSampleEntry ase; @@ -124,12 +133,44 @@ namespace Mist { }else if (thisTrack.codec == "MP3"){ ase.setCodec("mp4a"); ase.setDataReferenceIndex(1); + }else if (thisTrack.codec == "AC3"){ + ase.setCodec("ac-3"); + ase.setDataReferenceIndex(1); } ase.setSampleRate(thisTrack.rate); ase.setChannelCount(thisTrack.channels); ase.setSampleSize(thisTrack.size); + if (myMeta.tracks[*it].codec == "AC3"){ + MP4::DAC3 dac3Box; + switch (myMeta.tracks[*it].rate){ + case 48000: + dac3Box.setSampleRateCode(0); + break; + case 44100: + dac3Box.setSampleRateCode(1); + break; + case 32000: + dac3Box.setSampleRateCode(2); + break; + default: + dac3Box.setSampleRateCode(3); + break; + } + /// \todo the next settings are set to generic values, we might want to make these flexible + dac3Box.setBitStreamIdentification(8);//check the docs, this is a weird property + dac3Box.setBitStreamMode(0);//set to main, mixed audio + dac3Box.setAudioConfigMode(2);///\todo find out if ACMode should be different + if (thisTrack.channels > 4){ + dac3Box.setLowFrequencyEffectsChannelOn(1); + }else{ + dac3Box.setLowFrequencyEffectsChannelOn(0); + } + dac3Box.setFrameSizeCode(20);//should be OK, but test this. + ase.setCodecBox(dac3Box); + }else{//other codecs use the ESDS box MP4::ESDS esdsBox(thisTrack.init); ase.setCodecBox(esdsBox); + } stsdBox.setEntry(ase,0); } stblBox.setContent(stsdBox,offset++); @@ -138,6 +179,7 @@ namespace Mist { MP4::STTS sttsBox; sttsBox.setVersion(0); if (thisTrack.parts.size()){ + /// \todo Optimize for speed. We're currently parsing backwards, to prevent massive reallocs. Better would be to not set sampleCount to 1 for every single entry, calculate in advance, *then* set backwards. Volunteers? for (unsigned int part = thisTrack.parts.size(); part > 0; --part){ MP4::STTSEntry newEntry; newEntry.sampleCount = 1; @@ -441,6 +483,14 @@ namespace Mist { } void OutProgressiveMP4::onHTTP(){ + /*LTS-START*/ + //allow setting of max lead time through buffer variable. + //max lead time is set in MS, but the variable is in integer seconds for simplicity. + if (H.GetVar("buffer") != ""){ + maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000; + minSkipAhead = maxSkipAhead - std::min(2500u, maxSkipAhead / 2); + } + /*LTS-END*/ initialize(); parseData = true; wantRequest = false; diff --git a/src/output/output_progressive_mp4.h b/src/output/output_progressive_mp4.h index 367dbe15..a53a65a1 100644 --- a/src/output/output_progressive_mp4.h +++ b/src/output/output_progressive_mp4.h @@ -12,6 +12,9 @@ namespace Mist { if (trackID < rhs.trackID){ return true; } + if (trackID == rhs.trackID){ + return endTime < rhs.endTime; + } } return false; } diff --git a/src/output/output_rtmp.cpp b/src/output/output_rtmp.cpp index 239ca72e..b460bd1a 100644 --- a/src/output/output_rtmp.cpp +++ b/src/output/output_rtmp.cpp @@ -461,11 +461,12 @@ namespace Mist { Util::sanitizeName(streamName); //pull the server configuration + std::string smp = streamName.substr(0,(streamName.find_first_of("+ "))); IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE); ///< Contains server configuration and capabilities IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); configLock.wait(); - DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(streamName); + DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(smp); if (streamCfg){ if (streamCfg.getMember("source").asString().substr(0, 7) != "push://"){ DEBUG_MSG(DLVL_FAIL, "Push rejected - stream %s not a push-able stream. (%s != push://*)", streamName.c_str(), streamCfg.getMember("source").asString().c_str()); @@ -473,6 +474,23 @@ namespace Mist { }else{ std::string source = streamCfg.getMember("source").asString().substr(7); std::string IP = source.substr(0, source.find('@')); + /*LTS-START*/ + std::string password; + if (source.find('@') != std::string::npos){ + password = source.substr(source.find('@')+1); + if (password != ""){ + if (password == app_name){ + DEBUG_MSG(DLVL_DEVEL, "Password accepted - ignoring IP settings."); + IP = ""; + }else{ + DEBUG_MSG(DLVL_DEVEL, "Password rejected - checking IP."); + if (IP == ""){ + IP = "deny-all.invalid"; + } + } + } + } + /*LTS-END*/ if (IP != ""){ if (!myConn.isAddress(IP)){ DEBUG_MSG(DLVL_FAIL, "Push from %s to %s rejected - source host not whitelisted", myConn.getHost().c_str(), streamName.c_str()); diff --git a/src/output/output_rtsp.cpp b/src/output/output_rtsp.cpp new file mode 100644 index 00000000..dfb2b7cc --- /dev/null +++ b/src/output/output_rtsp.cpp @@ -0,0 +1,406 @@ +#include +#include +#include +#include "output_rtsp.h" + +namespace Mist { + OutRTSP::OutRTSP(Socket::Connection & myConn) : Output(myConn){ + connectedAt = Util::epoch() + 2208988800ll; + seekpoint = 0; + pausepoint = 0; + setBlocking(false); + maxSkipAhead = 0; + minSkipAhead = 0; + } + + /// Function used to send RTP packets over UDP + ///\param socket A UDP Connection pointer, sent as a void*, to keep portability. + ///\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) { + ((Socket::UDPConnection *) socket)->SendNow(data, len); + } + + + /// Function used to send RTP packets over TCP + ///\param socket A TCP Connection pointer, sent as a void*, to keep portability. + ///\param data The RTP Packet that needs to be sent + ///\param len The size of data + ///\param channel Used to distinguish different data streams when sending RTP over TCP + void sendTCP(void * socket, char * data, unsigned int len, unsigned int channel) { + //1 byte '$', 1 byte channel, 2 bytes length + char buf[] = "$$$$"; + buf[1] = channel; + ((short *) buf)[1] = htons(len); + ((Socket::Connection *) socket)->SendNow(buf, 4); + ((Socket::Connection *) socket)->SendNow(data, len); + } + + void OutRTSP::init(Util::Config * cfg){ + capa["name"] = "RTSP"; + capa["desc"] = "Provides Real Time Streaming Protocol output, supporting both UDP and TCP transports."; + capa["deps"] = ""; + capa["url_rel"] = "/$"; + capa["codecs"][0u][0u].append("H264"); + capa["codecs"][0u][1u].append("AAC"); + capa["codecs"][0u][1u].append("MP3"); + capa["codecs"][0u][1u].append("AC3"); + + capa["methods"][0u]["handler"] = "rtsp"; + capa["methods"][0u]["type"] = "rtsp"; + capa["methods"][0u]["priority"] = 2ll; + + cfg->addConnectorOptions(554, capa); + config = cfg; + } + + void OutRTSP::sendNext(){ + char * dataPointer = 0; + unsigned int dataLen = 0; + thisPacket.getString("data", dataPointer, dataLen); + unsigned int tid = thisPacket.getTrackId(); + unsigned int timestamp = thisPacket.getTime(); + + //update where we are now. + seekpoint = timestamp; + //if we're past the pausing point, seek to it, and pause immediately + if (pausepoint && seekpoint > pausepoint){ + seekpoint = pausepoint; + pausepoint = 0; + stop(); + return; + } + + void * socket = 0; + void (*callBack)(void *, char *, unsigned int, unsigned int) = 0; + + if (tracks[tid].UDP){ + socket = &tracks[tid].data; + callBack = sendUDP; + if (Util::epoch()/5 != tracks[tid].rtcpSent){ + tracks[tid].rtcpSent = Util::epoch()/5; + tracks[tid].rtpPacket.sendRTCP(connectedAt, &tracks[tid].rtcp, tid, myMeta, sendUDP); + } + }else{ + socket = &myConn; + callBack = sendTCP; + } + + if(myMeta.tracks[tid].codec == "AAC"){ + tracks[tid].rtpPacket.setTimestamp(timestamp * ((double) myMeta.tracks[tid].rate / 1000.0)); + tracks[tid].rtpPacket.sendAAC(socket, callBack, dataPointer, dataLen, tracks[tid].channel); + return; + } + + if(myMeta.tracks[tid].codec == "MP3" || myMeta.tracks[tid].codec == "AC3"){ + tracks[tid].rtpPacket.setTimestamp(timestamp * ((double) myMeta.tracks[tid].rate / 1000.0)); + tracks[tid].rtpPacket.sendRaw(socket, callBack, dataPointer, dataLen, tracks[tid].channel); + return; + } + + if(myMeta.tracks[tid].codec == "H264"){ + long long offset = thisPacket.getInt("offset"); + tracks[tid].rtpPacket.setTimestamp(90 * (timestamp + offset)); + if (tracks[tid].initSent && thisPacket.getFlag("keyframe")) { + MP4::AVCC avccbox; + avccbox.setPayload(myMeta.tracks[tid].init); + tracks[tid].rtpPacket.sendH264(socket, callBack, avccbox.getSPS(), avccbox.getSPSLen(), tracks[tid].channel); + tracks[tid].rtpPacket.sendH264(socket, callBack, avccbox.getPPS(), avccbox.getPPSLen(), tracks[tid].channel); + tracks[tid].initSent = true; + } + unsigned long sent = 0; + while (sent < dataLen) { + unsigned long nalSize = ntohl(*((unsigned long *)(dataPointer + sent))); + tracks[tid].rtpPacket.sendH264(socket, callBack, dataPointer + sent + 4, nalSize, tracks[tid].channel); + sent += nalSize + 4; + } + return; + } + + } + + void OutRTSP::onRequest(){ + while (HTTP_R.Read(myConn)){ + HTTP_S.Clean(); + HTTP_S.protocol = "RTSP/1.0"; + + //set the streamname and session + size_t found = HTTP_R.url.find('/', 7); + streamName = HTTP_R.url.substr(found + 1, HTTP_R.url.substr(found + 1).find('/')); + if (streamName != ""){ + HTTP_S.SetHeader("Session", Secure::md5(HTTP_S.GetHeader("User-Agent") + myConn.getHost()) + "_" + streamName); + } + + //set the date + time_t timer; + time(&timer); + struct tm * timeNow = gmtime(&timer); + char dString[42]; + strftime(dString, 42, "%a, %d %h %Y, %X GMT", timeNow); + HTTP_S.SetHeader("Date", dString); + + //set the sequence number to match the received sequence number + HTTP_S.SetHeader("CSeq", HTTP_R.GetHeader("CSeq")); + + //handle the request + DEBUG_MSG(DLVL_VERYHIGH, "Received %s:\n%s", HTTP_R.method.c_str(), HTTP_R.BuildRequest().c_str()); + bool handled = false; + if (HTTP_R.method == "OPTIONS"){ + HTTP_S.SetHeader("Public", "SETUP, TEARDOWN, PLAY, PAUSE, DESCRIBE, GET_PARAMETER"); + HTTP_S.SendResponse("200", "OK", myConn); + handled = true; + } + if (HTTP_R.method == "GET_PARAMETER"){ + HTTP_S.SendResponse("200", "OK", myConn); + handled = true; + } + if (HTTP_R.method == "DESCRIBE"){ + handleDescribe(); + handled = true; + } + if (HTTP_R.method == "SETUP"){ + handleSetup(); + handled = true; + } + if (HTTP_R.method == "PLAY"){ + handlePlay(); + handled = true; + } + if (HTTP_R.method == "PAUSE"){ + handlePause(); + handled = true; + } + if (HTTP_R.method == "TEARDOWN"){ + myConn.close(); + stop(); + handled = true; + } + if (!handled){ + DEBUG_MSG(DLVL_WARN, "Unhandled command %s:\n%s", HTTP_R.method.c_str(), HTTP_R.BuildRequest().c_str()); + } + HTTP_R.Clean(); + } + } + + void OutRTSP::handleDescribe(){ + //initialize the header, clear out any automatically selected tracks + initialize(); + selectedTracks.clear(); + + //calculate begin/end of stream + unsigned int firstms = myMeta.tracks.begin()->second.firstms; + unsigned int lastms = myMeta.tracks.begin()->second.lastms; + for (std::map::iterator objIt = myMeta.tracks.begin(); objIt != myMeta.tracks.end(); objIt ++) { + if (objIt->second.firstms < firstms){ + firstms = objIt->second.firstms; + } + if (objIt->second.lastms > lastms){ + lastms = objIt->second.lastms; + } + } + + HTTP_S.SetHeader("Content-Base", HTTP_R.url); + HTTP_S.SetHeader("Content-Type", "application/sdp"); + std::stringstream transportString; + transportString << "v=0\r\n"//version + "o=- "//owner + << Util::getMS()//id + << " 1 IN IP4 127.0.0.1"//or IPv6 + "\r\ns=" << streamName << "\r\n" + "c=IN IP4 0.0.0.0\r\n" + "i=Mistserver stream " << streamName << "\r\n" + "u=" << HTTP_R.url.substr(0, HTTP_R.url.rfind('/')) << "/" << streamName << "\r\n" + "t=0 0\r\n"//timing + "a=tool:MistServer\r\n"// + "a=type:broadcast\r\n"// + "a=control:*\r\n"// + "a=range:npt=" << ((double)firstms) / 1000.0 << "-" << ((double)lastms) / 1000.0 << "\r\n"; + + //loop over all tracks, add them to the SDP. + /// \todo Make sure this works correctly for multibitrate streams. + for (std::map::iterator objIt = myMeta.tracks.begin(); objIt != myMeta.tracks.end(); objIt ++) { + if (objIt->second.codec == "H264") { + MP4::AVCC avccbox; + avccbox.setPayload(objIt->second.init); + transportString << "m=" << objIt->second.type << " 0 RTP/AVP 97\r\n" + "a=rtpmap:97 H264/90000\r\n" + "a=cliprect:0,0," << objIt->second.height << "," << objIt->second.width << "\r\n" + "a=framesize:97 " << objIt->second.width << '-' << objIt->second.height << "\r\n" + "a=fmtp:97 packetization-mode=1;profile-level-id=" + << std::hex << std::setw(2) << std::setfill('0') << (int)objIt->second.init.data()[1] << std::dec << "E0" + << std::hex << std::setw(2) << std::setfill('0') << (int)objIt->second.init.data()[3] << std::dec << ";" + "sprop-parameter-sets=" + << Base64::encode(std::string(avccbox.getSPS(), avccbox.getSPSLen())) + << "," + << Base64::encode(std::string(avccbox.getPPS(), avccbox.getPPSLen())) + << "\r\n" + "a=framerate:" << ((double)objIt->second.fpks)/1000.0 << "\r\n" + "a=control:track" << objIt->second.trackID << "\r\n"; + } else if (objIt->second.codec == "AAC") { + transportString << "m=" << objIt->second.type << " 0 RTP/AVP 96" << "\r\n" + "a=rtpmap:96 mpeg4-generic/" << objIt->second.rate << "/" << objIt->second.channels << "\r\n" + "a=fmtp:96 streamtype=5; profile-level-id=15; config="; + for (unsigned int i = 0; i < objIt->second.init.size(); i++) { + transportString << std::hex << std::setw(2) << std::setfill('0') << (int)objIt->second.init[i] << std::dec; + } + //these values are described in RFC 3640 + transportString << "; mode=AAC-hbr; SizeLength=13; IndexLength=3; IndexDeltaLength=3;\r\n" + "a=control:track" << objIt->second.trackID << "\r\n"; + }else if (objIt->second.codec == "MP3") { + transportString << "m=" << objIt->second.type << " 0 RTP/AVP 96" << "\r\n" + "a=rtpmap:14 MPA/" << objIt->second.rate << "/" << objIt->second.channels << "\r\n" + //"a=fmtp:96 streamtype=5; profile-level-id=15;"; + //these values are described in RFC 3640 + //transportString << " mode=AAC-hbr; SizeLength=13; IndexLength=3; IndexDeltaLength=3;\r\n" + "a=control:track" << objIt->second.trackID << "\r\n"; + } + }//for tracks iterator + transportString << "\r\n"; + HTTP_S.SetBody(transportString.str()); + HTTP_S.SendResponse("200", "OK", myConn); + } + + void OutRTSP::handleSetup(){ + std::stringstream transportString; + unsigned int trId = atol(HTTP_R.url.substr(HTTP_R.url.rfind("/track") + 6).c_str()); + selectedTracks.insert(trId); + unsigned int SSrc = rand(); + if (myMeta.tracks[trId].codec == "H264") { + tracks[trId].rtpPacket = RTP::Packet(97, 1, 0, SSrc); + }else if(myMeta.tracks[trId].codec == "AAC" || myMeta.tracks[trId].codec == "MP3"){ + tracks[trId].rtpPacket = RTP::Packet(96, 1, 0, SSrc); + }else{ + DEBUG_MSG(DLVL_FAIL,"Unsupported codec for RTSP: %s",myMeta.tracks[trId].codec.c_str()); + } + + //read client ports + std::string transport = HTTP_R.GetHeader("Transport"); + unsigned long cPort; + if (transport.find("TCP") != std::string::npos) { + /// \todo This needs error checking. + tracks[trId].UDP = false; + std::string chanE = transport.substr(transport.find("interleaved=") + 12, (transport.size() - transport.rfind('-') - 1)); //extract channel ID + tracks[trId].channel = atol(chanE.c_str()); + tracks[trId].rtcpSent = 0; + transportString << transport; + } else { + tracks[trId].UDP = true; + size_t port_loc = transport.rfind("client_port=") + 12; + cPort = atol(transport.substr(port_loc, transport.rfind('-') - port_loc).c_str()); + //find available ports locally; + int sendbuff = 4*1024*1024; + tracks[trId].data.SetDestination(myConn.getHost(), cPort); + tracks[trId].data.bind(2000 + trId * 2); + setsockopt(tracks[trId].data.getSock(), SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff)); + tracks[trId].rtcp.SetDestination(myConn.getHost(), cPort + 1); + tracks[trId].rtcp.bind(2000 + trId * 2 + 1); + setsockopt(tracks[trId].rtcp.getSock(), SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff)); + std::string source = HTTP_R.url.substr(7); + unsigned int loc = std::min(source.find(':'),source.find('/')); + source = source.substr(0,loc); + transportString << "RTP/AVP/UDP;unicast;client_port=" << cPort << '-' << cPort + 1 << ";source="<< source <<";server_port=" << (2000 + trId * 2) << "-" << (2000 + trId * 2 + 1) << ";ssrc=" << std::hex << SSrc << std::dec; + } + /// \todo We should probably not allocate UDP sockets when using TCP. + HTTP_S.SetHeader("Expires", HTTP_S.GetHeader("Date")); + HTTP_S.SetHeader("Transport", transportString.str()); + HTTP_S.SetHeader("Cache-Control", "no-cache"); + HTTP_S.SendResponse("200", "OK", myConn); + } + + void OutRTSP::handlePause(){ + HTTP_S.SendResponse("200", "OK", myConn); + std::string range = HTTP_R.GetHeader("Range"); + if (range.empty()){ + stop(); + return; + } + range = range.substr(range.find("npt=")+4); + if (range.empty()) { + stop(); + return; + } + pausepoint = 1000 * (int) atof(range.c_str()); + if (pausepoint > seekpoint){ + seekpoint = pausepoint; + pausepoint = 0; + stop(); + } + } + + void OutRTSP::handlePlay(){ + /// \todo Add support for queuing multiple play ranges + //calculate first and last possible timestamps + unsigned int firstms = myMeta.tracks.begin()->second.firstms; + unsigned int lastms = myMeta.tracks.begin()->second.lastms; + for (std::map::iterator objIt = myMeta.tracks.begin(); objIt != myMeta.tracks.end(); objIt ++) { + if (objIt->second.firstms < firstms){ + firstms = objIt->second.firstms; + } + if (objIt->second.lastms > lastms){ + lastms = objIt->second.lastms; + } + } + + std::stringstream transportString; + std::string range = HTTP_R.GetHeader("Range"); + if (range != ""){ + DEBUG_MSG(DLVL_DEVEL, "Play: %s", range.c_str()); + range = range.substr(range.find("npt=")+4); + if (range.empty()) { + seekpoint = 0; + } else { + range = range.substr(0, range.find('-')); + seekpoint = 1000 * (int) atof(range.c_str()); + } + //snap seekpoint to closest keyframe + for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { + it->second.rtcpSent =0; + if (myMeta.tracks[it->first].type == "video") { + unsigned int newPoint = seekpoint; + for (unsigned int iy = 0; iy < myMeta.tracks[it->first].keys.size(); iy++) { + if (myMeta.tracks[it->first].keys[iy].getTime() > seekpoint && iy > 0) { + iy--; + break; + } + newPoint = myMeta.tracks[it->first].keys[iy].getTime(); + } + seekpoint = newPoint; + break; + } + } + } + seek(seekpoint); + + unsigned int counter = 0; + std::map timeMap; //Keeps track of temporary timestamp data for the upcoming seek. + for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { + timeMap[it->first] = myMeta.tracks[it->first].firstms; + for (unsigned int iy = 0; iy < myMeta.tracks[it->first].parts.size(); iy++) { + if (timeMap[it->first] > seekpoint) { + iy--; + break; + } + timeMap[it->first] += myMeta.tracks[it->first].parts[iy].getDuration();//door parts van keyframes + } + if (myMeta.tracks[it->first].codec == "H264") { + timeMap[it->first] = 90 * timeMap[it->first]; + } else if (myMeta.tracks[it->first].codec == "AAC" || myMeta.tracks[it->first].codec == "MP3" || myMeta.tracks[it->first].codec == "AC3") { + timeMap[it->first] = timeMap[it->first] * ((double)myMeta.tracks[it->first].rate / 1000.0); + } + transportString << "url=" << HTTP_R.url.substr(0, HTTP_R.url.rfind('/')) << "/" << streamName << "/track" << it->first << ";"; //get the current url, not localhost + transportString << "sequence=" << tracks[it->first].rtpPacket.getSequence() << ";rtptime=" << timeMap[it->first]; + if (counter < tracks.size()) { + transportString << ","; + } + counter++; + } + std::stringstream rangeStr; + rangeStr << "npt=" << seekpoint/1000 << "." << std::setw(3) << std::setfill('0') << seekpoint %1000 << "-" << std::setw(1) << lastms/1000 << "." << std::setw(3) << std::setfill('0') << lastms%1000; + HTTP_S.SetHeader("Range", rangeStr.str()); + HTTP_S.SetHeader("RTP-Info", transportString.str()); + HTTP_S.SendResponse("200", "OK", myConn); + parseData = true; + } + +} diff --git a/src/output/output_rtsp.h b/src/output/output_rtsp.h new file mode 100644 index 00000000..7fc2b5b7 --- /dev/null +++ b/src/output/output_rtsp.h @@ -0,0 +1,47 @@ +#pragma once + +#include "output.h" +#include +#include +#include + +namespace Mist { + ///Structure used to keep track of selected tracks. + class trackmeta { + public: + trackmeta(){ + rtcpSent = 0; + channel = 0; + UDP = false; + initSent = false; + } + Socket::UDPConnection data; + Socket::UDPConnection rtcp; + RTP::Packet rtpPacket;/// The RTP packet instance used for this track. + long long rtcpSent; + int channel;/// Channel number, used in TCP sending + bool UDP;/// True if sending over UDP, false otherwise + bool initSent; + }; + + class OutRTSP : public Output { + public: + OutRTSP(Socket::Connection & myConn); + static void init(Util::Config * cfg); + void sendNext(); + void onRequest(); + private: + void handleDescribe(); + void handleSetup(); + void handlePlay(); + void handlePause(); + + long long connectedAt;///< The timestamp the connection was made, as reference point for RTCP packets. + std::map tracks;///< List of selected tracks with RTSP-specific session data. + unsigned int seekpoint;///< Current play position + unsigned int pausepoint;///< Position to pause at, when reached + HTTP::Parser HTTP_R, HTTP_S; + }; +} + +typedef Mist::OutRTSP mistOut; diff --git a/src/output/output_ts.cpp b/src/output/output_ts.cpp index 516a32cc..4f74bc65 100644 --- a/src/output/output_ts.cpp +++ b/src/output/output_ts.cpp @@ -44,9 +44,11 @@ namespace Mist { capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces"; capa["optional"]["tracks"]["type"] = "str"; capa["optional"]["tracks"]["option"] = "--tracks"; + capa["codecs"][0u][0u].append("HEVC"); capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("MP3"); + capa["codecs"][0u][1u].append("AC3"); cfg->addOption("streamname", JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); cfg->addOption("tracks", diff --git a/src/output/output_ts_base.cpp b/src/output/output_ts_base.cpp index 267247dc..ef7998d5 100644 --- a/src/output/output_ts_base.cpp +++ b/src/output/output_ts_base.cpp @@ -4,6 +4,7 @@ namespace Mist { TSOutput::TSOutput(Socket::Connection & conn) : TS_BASECLASS(conn){ packCounter=0; haveAvcc = false; + haveHvcc = false; until=0xFFFFFFFFFFFFFFFFull; setBlocking(true); sendRepeatingHeaders = false; @@ -81,6 +82,16 @@ namespace Mist { bs = avccbox.asAnnexB(); extraSize += bs.size(); } + /*LTS-START*/ + if (myMeta.tracks[thisPacket.getTrackId()].codec == "HEVC"){ + if (!haveHvcc){ + hvccbox.setPayload(myMeta.tracks[thisPacket.getTrackId()].init); + haveHvcc = true; + } + bs = hvccbox.asAnnexB(); + extraSize += bs.size(); + } + /*LTS-END*/ } unsigned int watKunnenWeIn1Ding = 65490-13; @@ -106,6 +117,13 @@ namespace Mist { fillPacket(bs.data(), bs.size()); alreadySent += bs.size(); } + /*LTS-START*/ + if (myMeta.tracks[thisPacket.getTrackId()].codec == "HEVC"){ + bs = hvccbox.asAnnexB(); + fillPacket(bs.data(), bs.size()); + alreadySent += bs.size(); + } + /*LTS-END*/ } } while (i + 4 < (unsigned int)dataLen){ diff --git a/src/output/output_ts_base.h b/src/output/output_ts_base.h index 1f7bbfa0..7460ced0 100644 --- a/src/output/output_ts_base.h +++ b/src/output/output_ts_base.h @@ -25,6 +25,10 @@ namespace Mist { bool haveAvcc; MP4::AVCC avccbox; bool appleCompat; + /*LTS-START*/ + bool haveHvcc; + MP4::HVCC hvccbox; + /*LTS-END*/ bool sendRepeatingHeaders; long long unsigned int until; long long unsigned int lastVid; diff --git a/src/output/output_ts_push.cpp b/src/output/output_ts_push.cpp new file mode 100644 index 00000000..41ace0e4 --- /dev/null +++ b/src/output/output_ts_push.cpp @@ -0,0 +1,99 @@ +#include "output_ts_push.h" +#include +#include + +namespace Mist { + OutTSPush::OutTSPush(Socket::Connection & conn) : TSOutput(conn){ + streamName = config->getString("streamname"); + parseData = true; + wantRequest = false; + sendRepeatingHeaders = true; + initialize(); + std::string tracks = config->getString("tracks"); + unsigned int currTrack = 0; + //loop over tracks, add any found track IDs to selectedTracks + if (tracks != ""){ + selectedTracks.clear(); + for (unsigned int i = 0; i < tracks.size(); ++i){ + if (tracks[i] >= '0' && tracks[i] <= '9'){ + currTrack = currTrack*10 + (tracks[i] - '0'); + }else{ + if (currTrack > 0){ + selectedTracks.insert(currTrack); + } + currTrack = 0; + } + } + if (currTrack > 0){ + selectedTracks.insert(currTrack); + } + } + + //For udp pushing, 7 ts packets a time + packetBuffer.reserve(config->getInteger("udpsize") * 188); + std::string host = config->getString("destination"); + if (host.substr(0, 6) == "udp://"){ + host = host.substr(6); + } + int port = atoi(host.substr(host.find(":") + 1).c_str()); + host = host.substr(0, host.find(":")); + pushSock.SetDestination(host, port); + } + + OutTSPush::~OutTSPush() {} + + void OutTSPush::init(Util::Config * cfg){ + Output::init(cfg); + capa["name"] = "TSPush"; + capa["desc"] = "Push raw MPEG/TS over a TCP or UDP socket."; + capa["deps"] = ""; + capa["required"]["streamname"]["name"] = "Stream"; + capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add this protocol multiple times using different ports."; + capa["required"]["streamname"]["type"] = "str"; + capa["required"]["streamname"]["option"] = "--stream"; + capa["required"]["destination"]["name"] = "Destination"; + capa["required"]["destination"]["help"] = "Where to push to, in the format protocol://hostname:port. Ie: udp://127.0.0.1:9876"; + capa["required"]["destination"]["type"] = "str"; + capa["required"]["destination"]["option"] = "--destination"; + capa["required"]["udpsize"]["name"] = "UDP Size"; + capa["required"]["udpsize"]["help"] = "The number of TS packets to push in a single UDP datagram"; + capa["required"]["udpsize"]["type"] = "uint"; + capa["required"]["udpsize"]["default"] = 5; + capa["required"]["udpsize"]["option"] = "--udpsize"; + capa["optional"]["tracks"]["name"] = "Tracks"; + capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces"; + capa["optional"]["tracks"]["type"] = "str"; + capa["optional"]["tracks"]["option"] = "--tracks"; + capa["codecs"][0u][0u].append("HEVC"); + capa["codecs"][0u][0u].append("H264"); + capa["codecs"][0u][1u].append("AAC"); + capa["codecs"][0u][1u].append("MP3"); + cfg->addBasicConnectorOptions(capa); + cfg->addOption("streamname", + JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); + cfg->addOption("destination", + JSON::fromString("{\"arg\":\"string\",\"short\":\"D\",\"long\":\"destination\",\"help\":\"Where to push to, in the format protocol://hostname:port. Ie: udp://127.0.0.1:9876\"}")); + cfg->addOption("tracks", + JSON::fromString("{\"arg\":\"string\",\"value\":[\"\"],\"short\": \"t\",\"long\":\"tracks\",\"help\":\"The track IDs of the stream that this connector will transmit separated by spaces.\"}")); + cfg->addOption("udpsize", + JSON::fromString("{\"arg\":\"integer\",\"value\":5,\"short\": \"u\",\"long\":\"udpsize\",\"help\":\"The number of TS packets to push in a single UDP datagram.\"}")); + config = cfg; + } + + void OutTSPush::fillBuffer(const char * data, size_t dataLen){ + static int curFilled = 0; + if (curFilled == config->getInteger("udpsize")){ + pushSock.SendNow(packetBuffer); + packetBuffer.clear(); + packetBuffer.reserve(config->getInteger("udpsize") * 188); + curFilled = 0; + } + packetBuffer += std::string(data, 188); + curFilled ++; + } + + void OutTSPush::sendTS(const char * tsData, unsigned int len){ + fillBuffer(tsData, len); + } + +} diff --git a/src/output/output_ts_push.h b/src/output/output_ts_push.h new file mode 100644 index 00000000..e9f4ee08 --- /dev/null +++ b/src/output/output_ts_push.h @@ -0,0 +1,18 @@ +#include "output_ts_base.h" + +namespace Mist { + class OutTSPush : public TSOutput{ + public: + OutTSPush(Socket::Connection & conn); + ~OutTSPush(); + static void init(Util::Config * cfg); + static bool listenMode(){return false;} + void sendTS(const char * tsData, unsigned int len=188); + protected: + void fillBuffer(const char * data, size_t dataLen); + std::string packetBuffer; + Socket::UDPConnection pushSock; + }; +} + +typedef Mist::OutTSPush mistOut;