LTS Commits

This commit is contained in:
Thulinma 2015-04-05 21:38:36 +02:00
parent f24d97b510
commit 4bdbd82f66
72 changed files with 8245 additions and 105 deletions

View file

@ -68,6 +68,24 @@ if (NOT DEFINED ${NOSHM} )
add_definitions(-DSHM_ENABLED=1) add_definitions(-DSHM_ENABLED=1)
endif() 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 # # Build Variables - Thread Names #
######################################## ########################################
@ -95,12 +113,16 @@ set(libHeaders
${SOURCE_DIR}/lib/converter.h ${SOURCE_DIR}/lib/converter.h
${SOURCE_DIR}/lib/defines.h ${SOURCE_DIR}/lib/defines.h
${SOURCE_DIR}/lib/dtsc.h ${SOURCE_DIR}/lib/dtsc.h
${SOURCE_DIR}/lib/encryption.h
${SOURCE_DIR}/lib/filesystem.h ${SOURCE_DIR}/lib/filesystem.h
${SOURCE_DIR}/lib/flv_tag.h ${SOURCE_DIR}/lib/flv_tag.h
${SOURCE_DIR}/lib/ftp.h ${SOURCE_DIR}/lib/ftp.h
${SOURCE_DIR}/lib/http_parser.h ${SOURCE_DIR}/lib/http_parser.h
${SOURCE_DIR}/lib/json.h ${SOURCE_DIR}/lib/json.h
${SOURCE_DIR}/lib/mp4_adobe.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_generic.h
${SOURCE_DIR}/lib/mp4.h ${SOURCE_DIR}/lib/mp4.h
${SOURCE_DIR}/lib/mp4_ms.h ${SOURCE_DIR}/lib/mp4_ms.h
@ -108,6 +130,7 @@ set(libHeaders
${SOURCE_DIR}/lib/ogg.h ${SOURCE_DIR}/lib/ogg.h
${SOURCE_DIR}/lib/procs.h ${SOURCE_DIR}/lib/procs.h
${SOURCE_DIR}/lib/rtmpchunks.h ${SOURCE_DIR}/lib/rtmpchunks.h
${SOURCE_DIR}/lib/rtp.h
${SOURCE_DIR}/lib/shared_memory.h ${SOURCE_DIR}/lib/shared_memory.h
${SOURCE_DIR}/lib/socket.h ${SOURCE_DIR}/lib/socket.h
${SOURCE_DIR}/lib/stream.h ${SOURCE_DIR}/lib/stream.h
@ -131,6 +154,7 @@ set(libSources
${SOURCE_DIR}/lib/converter.cpp ${SOURCE_DIR}/lib/converter.cpp
${SOURCE_DIR}/lib/dtsc.cpp ${SOURCE_DIR}/lib/dtsc.cpp
${SOURCE_DIR}/lib/dtscmeta.cpp ${SOURCE_DIR}/lib/dtscmeta.cpp
${SOURCE_DIR}/lib/encryption.cpp
${SOURCE_DIR}/lib/filesystem.cpp ${SOURCE_DIR}/lib/filesystem.cpp
${SOURCE_DIR}/lib/flv_tag.cpp ${SOURCE_DIR}/lib/flv_tag.cpp
${SOURCE_DIR}/lib/ftp.cpp ${SOURCE_DIR}/lib/ftp.cpp
@ -138,12 +162,15 @@ set(libSources
${SOURCE_DIR}/lib/json.cpp ${SOURCE_DIR}/lib/json.cpp
${SOURCE_DIR}/lib/mp4_adobe.cpp ${SOURCE_DIR}/lib/mp4_adobe.cpp
${SOURCE_DIR}/lib/mp4.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_generic.cpp
${SOURCE_DIR}/lib/mp4_ms.cpp ${SOURCE_DIR}/lib/mp4_ms.cpp
${SOURCE_DIR}/lib/nal.cpp ${SOURCE_DIR}/lib/nal.cpp
${SOURCE_DIR}/lib/ogg.cpp ${SOURCE_DIR}/lib/ogg.cpp
${SOURCE_DIR}/lib/procs.cpp ${SOURCE_DIR}/lib/procs.cpp
${SOURCE_DIR}/lib/rtmpchunks.cpp ${SOURCE_DIR}/lib/rtmpchunks.cpp
${SOURCE_DIR}/lib/rtp.cpp
${SOURCE_DIR}/lib/shared_memory.cpp ${SOURCE_DIR}/lib/shared_memory.cpp
${SOURCE_DIR}/lib/socket.cpp ${SOURCE_DIR}/lib/socket.cpp
${SOURCE_DIR}/lib/stream.cpp ${SOURCE_DIR}/lib/stream.cpp
@ -153,7 +180,6 @@ set(libSources
${SOURCE_DIR}/lib/ts_packet.cpp ${SOURCE_DIR}/lib/ts_packet.cpp
${SOURCE_DIR}/lib/vorbis.cpp ${SOURCE_DIR}/lib/vorbis.cpp
) )
######################################## ########################################
# MistLib - Build # # MistLib - Build #
######################################## ########################################
@ -206,6 +232,9 @@ makeAnalyser(DTSC dtsc)
makeAnalyser(AMF amf) makeAnalyser(AMF amf)
makeAnalyser(MP4 mp4) makeAnalyser(MP4 mp4)
makeAnalyser(OGG ogg) makeAnalyser(OGG ogg)
makeAnalyser(RTP rtp) #LTS
makeAnalyser(RTSP rtsp_rtp) #LTS
makeAnalyser(Stats stats) #LTS
add_executable(MistInfo add_executable(MistInfo
src/analysers/info.cpp src/analysers/info.cpp
) )
@ -244,6 +273,10 @@ makeInput(MP3 mp3)
makeInput(FLV flv) makeInput(FLV flv)
makeInput(OGG ogg) makeInput(OGG ogg)
makeInput(Buffer buffer) makeInput(Buffer buffer)
makeInput(ISMV ismv)#LTS
makeInput(MP4 mp4)#LTS
makeInput(TS ts)#LTS
makeInput(Folder folder)#LTS
######################################## ########################################
# MistServer - Outputs # # MistServer - Outputs #
@ -292,6 +325,10 @@ makeOutput(JSON json http)
makeOutput(TS ts ts) makeOutput(TS ts ts)
makeOutput(HTTPTS httpts http ts) makeOutput(HTTPTS httpts http ts)
makeOutput(HLS hls http ts) makeOutput(HLS hls http ts)
makeOutput(RTSP rtsp)#LTS
makeOutput(TSPush ts_push ts)#LTS
makeOutput(DASH dash_mp4 http)#LTS
add_executable(MistOutHTTP add_executable(MistOutHTTP
src/output/mist_out.cpp src/output/mist_out.cpp
src/output/output.cpp src/output/output.cpp
@ -388,10 +425,13 @@ add_custom_target(localSettingsPage
# MistController - Header Files # # MistController - Header Files #
######################################## ########################################
set(controllerHeaders 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_api.h
${SOURCE_DIR}/src/controller/controller_statistics.h ${SOURCE_DIR}/src/controller/controller_statistics.h
${SOURCE_DIR}/src/controller/controller_connectors.h ${SOURCE_DIR}/src/controller/controller_connectors.h
${SOURCE_DIR}/src/controller/controller_storage.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_capabilities.h
${SOURCE_DIR}/src/controller/controller_streams.h ${SOURCE_DIR}/src/controller/controller_streams.h
) )
@ -401,14 +441,16 @@ set(controllerHeaders
######################################## ########################################
set(controllerSources set(controllerSources
${SOURCE_DIR}/src/controller/controller.cpp ${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_streams.cpp
${SOURCE_DIR}/src/controller/controller_storage.cpp ${SOURCE_DIR}/src/controller/controller_storage.cpp
${SOURCE_DIR}/src/controller/controller_connectors.cpp ${SOURCE_DIR}/src/controller/controller_connectors.cpp
${SOURCE_DIR}/src/controller/controller_statistics.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_capabilities.cpp
${SOURCE_DIR}/src/controller/controller_uplink.cpp
${SOURCE_DIR}/src/controller/controller_api.cpp ${SOURCE_DIR}/src/controller/controller_api.cpp
) )
######################################## ########################################
# MistController - Build # # MistController - Build #
######################################## ########################################

124
Makefile
View file

@ -7,13 +7,30 @@ libdir = $(exec_prefix)/lib
PACKAGE_VERSION := $(shell git describe --tags 2> /dev/null || cat VERSION 2> /dev/null || echo "Unknown") PACKAGE_VERSION := $(shell git describe --tags 2> /dev/null || cat VERSION 2> /dev/null || echo "Unknown")
DEBUG = 4 DEBUG = 4
RELEASE = Generic_$(shell getconf LONG_BIT) RELEASE = Generic_$(shell getconf LONG_BIT)
GEOIP= # /*LTS*/
ifeq ($(PACKAGE_VERSION),Unknown) ifeq ($(PACKAGE_VERSION),Unknown)
$(warning Version is unknown - consider creating a VERSION file or fixing your git setup.) $(warning Version is unknown - consider creating a VERSION file or fixing your git setup.)
endif endif
CPPFLAGS = -Wall -g -O2 -fPIC 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 ifndef NOSHM
override CPPFLAGS += -DSHM_ENABLED=1 override CPPFLAGS += -DSHM_ENABLED=1
@ -27,6 +44,21 @@ THREADLIB = -lpthread -lrt
LDLIBS = ${THREADLIB} LDLIBS = ${THREADLIB}
LDFLAGS = -I${includedir} -L${libdir} -lmist 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 .DEFAULT_GOAL := all
@ -42,6 +74,7 @@ endif
controller: MistController controller: MistController
MistController: override LDLIBS += $(THREADLIB) MistController: override LDLIBS += $(THREADLIB)
MistController: override LDLIBS += $(GEOIP) # /*LTS*/
MistController: src/controller/server.html.h src/controller/* MistController: src/controller/server.html.h src/controller/*
$(CXX) $(LDFLAGS) $(CPPFLAGS) src/controller/*.cpp $(LDLIBS) -o $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) src/controller/*.cpp $(LDLIBS) -o $@
@ -49,6 +82,22 @@ analysers: MistAnalyserRTMP
MistAnalyserRTMP: src/analysers/rtmp_analyser.cpp MistAnalyserRTMP: src/analysers/rtmp_analyser.cpp
$(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ $(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 analysers: MistAnalyserFLV
MistAnalyserFLV: src/analysers/flv_analyser.cpp MistAnalyserFLV: src/analysers/flv_analyser.cpp
$(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
@ -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 MistInDTSC: src/input/mist_in.cpp src/input/input.cpp src/input/input_dtsc.cpp src/io.cpp
$(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ $(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 inputs: MistInMP3
MistInMP3: override LDLIBS += $(THREADLIB) MistInMP3: override LDLIBS += $(THREADLIB)
MistInMP3: override CPPFLAGS += "-DINPUTTYPE=\"input_mp3.h\"" 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 MistInFLV: src/input/mist_in.cpp src/input/input.cpp src/input/input_flv.cpp src/io.cpp
$(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ $(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 inputs: MistInOGG
MistInOGG: override LDLIBS += $(THREADLIB) MistInOGG: override LDLIBS += $(THREADLIB)
MistInOGG: override CPPFLAGS += "-DINPUTTYPE=\"input_ogg.h\"" MistInOGG: override CPPFLAGS += "-DINPUTTYPE=\"input_ogg.h\""
@ -103,12 +170,33 @@ 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 MistInBuffer: src/input/mist_in.cpp src/input/input.cpp src/input/input_buffer.cpp src/io.cpp
$(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ $(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 outputs: MistOutFLV
MistOutFLV: override LDLIBS += $(THREADLIB) MistOutFLV: override LDLIBS += $(THREADLIB)
MistOutFLV: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutFLV: override CPPFLAGS += "-DOUTPUTTYPE=\"output_progressive_flv.h\"" 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 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 $@ $(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 outputs: MistOutOGG
MistOutOGG: override LDLIBS += $(THREADLIB) MistOutOGG: override LDLIBS += $(THREADLIB)
MistOutOGG: override LDLIBS += $(GEOIP) MistOutOGG: override LDLIBS += $(GEOIP)
@ -118,72 +206,98 @@ MistOutOGG: src/output/mist_out.cpp src/output/output_http.cpp src/output/output
outputs: MistOutMP4 outputs: MistOutMP4
MistOutMP4: override LDLIBS += $(THREADLIB) MistOutMP4: override LDLIBS += $(THREADLIB)
MistOutMP4: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutMP4: override CPPFLAGS += "-DOUTPUTTYPE=\"output_progressive_mp4.h\"" 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 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 $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
outputs: MistOutMP3 outputs: MistOutMP3
MistOutMP3: override LDLIBS += $(THREADLIB) MistOutMP3: override LDLIBS += $(THREADLIB)
MistOutMP3: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutMP3: override CPPFLAGS += "-DOUTPUTTYPE=\"output_progressive_mp3.h\"" 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 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 $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
outputs: MistOutRTMP outputs: MistOutRTMP
MistOutRTMP: override LDLIBS += $(THREADLIB) MistOutRTMP: override LDLIBS += $(THREADLIB)
MistOutRTMP: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutRTMP: override CPPFLAGS += "-DOUTPUTTYPE=\"output_rtmp.h\"" 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 MistOutRTMP: src/output/mist_out.cpp src/output/output.cpp src/output/output_rtmp.cpp src/io.cpp
$(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
outputs: MistOutRaw outputs: MistOutRaw
MistOutRaw: override LDLIBS += $(THREADLIB) MistOutRaw: override LDLIBS += $(THREADLIB)
MistOutRaw: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutRaw: override CPPFLAGS += "-DOUTPUTTYPE=\"output_raw.h\"" 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 MistOutRaw: src/output/mist_out.cpp src/output/output.cpp src/output/output_raw.cpp src/io.cpp
$(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
outputs: MistOutHTTPTS outputs: MistOutHTTPTS
MistOutHTTPTS: override LDLIBS += $(THREADLIB) MistOutHTTPTS: override LDLIBS += $(THREADLIB)
MistOutHTTPTS: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutHTTPTS: override CPPFLAGS += -DOUTPUTTYPE=\"output_httpts.h\" -DTS_BASECLASS=HTTPOutput 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 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 $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
outputs: MistOutTS outputs: MistOutTS
MistOutTS: override LDLIBS += $(THREADLIB) MistOutTS: override LDLIBS += $(THREADLIB)
MistOutTS: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutTS: override CPPFLAGS += "-DOUTPUTTYPE=\"output_ts.h\"" 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 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 $@ $(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 outputs: MistOutHTTP
MistOutHTTP: override LDLIBS += $(THREADLIB) MistOutHTTP: override LDLIBS += $(THREADLIB)
MistOutHTTP: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutHTTP: override CPPFLAGS += "-DOUTPUTTYPE=\"output_http_internal.h\"" 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 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 outputs: MistOutHSS
MistOutHSS: override LDLIBS += $(THREADLIB) MistOutHSS: override LDLIBS += $(THREADLIB)
MistOutHSS: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutHSS: override CPPFLAGS += "-DOUTPUTTYPE=\"output_hss.h\"" 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 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 $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
outputs: MistOutHLS outputs: MistOutHLS
MistOutHLS: override LDLIBS += $(THREADLIB) MistOutHLS: override LDLIBS += $(THREADLIB)
MistOutHLS: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutHLS: override CPPFLAGS += -DOUTPUTTYPE=\"output_hls.h\" -DTS_BASECLASS=HTTPOutput 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 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 $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
outputs: MistOutHDS outputs: MistOutHDS
MistOutHDS: override LDLIBS += $(THREADLIB) MistOutHDS: override LDLIBS += $(THREADLIB)
MistOutHDS: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutHDS: override CPPFLAGS += "-DOUTPUTTYPE=\"output_hds.h\"" 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 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 $@ $(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 outputs: MistOutSRT
MistOutSRT: override LDLIBS += $(THREADLIB) MistOutSRT: override LDLIBS += $(THREADLIB)
MistOutSRT: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutSRT: override CPPFLAGS += "-DOUTPUTTYPE=\"output_srt.h\"" 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 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 $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
outputs: MistOutJSON outputs: MistOutJSON
MistOutJSON: override LDLIBS += $(THREADLIB) MistOutJSON: override LDLIBS += $(THREADLIB)
MistOutJSON: override LDLIBS += $(GEOIP) # /*LTS*/
MistOutJSON: override CPPFLAGS += "-DOUTPUTTYPE=\"output_json.h\"" 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 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 $@ $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@
@ -203,9 +317,9 @@ endif
sourcery: src/sourcery.cpp sourcery: src/sourcery.cpp
$(CXX) -o $@ $(CPPFLAGS) $^ $(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 $(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 rm embed.min.js
src/controller/server.html: $(lspDATA) $(lspSOURCES) $(lspSOURCESmin) src/controller/server.html: $(lspDATA) $(lspSOURCES) $(lspSOURCESmin)
@ -219,7 +333,7 @@ src/controller/server.html: $(lspDATA) $(lspSOURCES) $(lspSOURCESmin)
cat lsp/footer.html >> $@ cat lsp/footer.html >> $@
src/controller/server.html.h: src/controller/server.html sourcery 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)) lib_objects := $(patsubst %.cpp,%.o,$(wildcard lib/*.cpp))

View file

@ -356,6 +356,11 @@ void DTSC::Stream::cutOneBuffer() {
metadata.tracks[trid].fragments.clear(); metadata.tracks[trid].fragments.clear();
} }
} }
/*LTS-START*/
if (!recordPath.empty()) {
recordPacket(buffers.begin()->second);
}
/*LTS-END*/
deletionCallback(buffers.begin()->first); deletionCallback(buffers.begin()->first);
buffers.erase(buffers.begin()); buffers.erase(buffers.begin());
} }
@ -453,6 +458,35 @@ DTSC::Ring * DTSC::Stream::getRing() {
return new DTSC::Ring(tmp); 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. /// Deletes a given out Ring class from memory and internal Ring list.
/// Checks for NULL pointers and invalid pointers, silently discarding them. /// Checks for NULL pointers and invalid pointers, silently discarding them.
void DTSC::Stream::dropRing(DTSC::Ring * ptr) { void DTSC::Stream::dropRing(DTSC::Ring * ptr) {
@ -764,16 +798,13 @@ void DTSC::File::seekNext() {
myPack.null(); myPack.null();
return; return;
} }
fseek(F, currentPositions.begin()->bytePos, SEEK_SET); seekPos thisPos = *currentPositions.begin();
fseek(F, thisPos.bytePos, SEEK_SET);
if (reachedEOF()) { if (reachedEOF()) {
myPack.null(); myPack.null();
return; return;
} }
clearerr(F); 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()); currentPositions.erase(currentPositions.begin());
lastreadpos = ftell(F); lastreadpos = ftell(F);
if (fread(buffer, 4, 1, F) != 1) { if (fread(buffer, 4, 1, F) != 1) {
@ -786,7 +817,7 @@ void DTSC::File::seekNext() {
return; return;
} }
if (memcmp(buffer, DTSC::Magic_Header, 4) == 0) { 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(); return seekNext();
} }
long long unsigned int version = 0; long long unsigned int version = 0;
@ -864,9 +895,12 @@ void DTSC::File::seekNext() {
} }
currentPositions.insert(tmpPos); currentPositions.insert(tmpPos);
} else { } else {
seek_time(myPack.getTime() + 1, myPack.getTrackId(), true); seek_time(myPack.getTime(), myPack.getTrackId(), true);
} }
seek_bpos(tempLoc); 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) { bool DTSC::File::seek_time(unsigned int ms, unsigned int trackNo, bool forceSeek) {
seekPos tmpPos; seekPos tmpPos;
tmpPos.trackID = trackNo; 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.seekTime = myPack.getTime();
tmpPos.bytePos = getBytePos(); tmpPos.bytePos = getBytePos();
/*
if (trackNo == myPack.getTrackId()){
tmpPos.bytePos += myPack.getDataLen();
}
*/
} else { } else {
tmpPos.seekTime = 0; tmpPos.seekTime = 0;
tmpPos.bytePos = 0; tmpPos.bytePos = 0;

View file

@ -179,6 +179,26 @@ namespace DTSC {
volatile int playCount; 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. ///\brief Basic class for storage of data associated with single DTSC packets, a.k.a. parts.
class Part { class Part {
@ -270,6 +290,7 @@ namespace DTSC {
std::deque<Key> keys; std::deque<Key> keys;
std::deque<unsigned long> keySizes; std::deque<unsigned long> keySizes;
std::deque<Part> parts; std::deque<Part> parts;
std::deque<Ivec> ivecs; /*LTS*/
Key & getKey(unsigned int keyNum); Key & getKey(unsigned int keyNum);
unsigned int timeToKeynum(unsigned int timestamp); unsigned int timeToKeynum(unsigned int timestamp);
unsigned int timeToFragnum(unsigned int timestamp); unsigned int timeToFragnum(unsigned int timestamp);
@ -394,6 +415,7 @@ namespace DTSC {
std::string & outPacket(livePos num); std::string & outPacket(livePos num);
std::string & outHeader(); std::string & outHeader();
Ring * getRing(); Ring * getRing();
void setRecord(std::string path); /*LTS*/
unsigned int getTime(); unsigned int getTime();
void dropRing(Ring * ptr); void dropRing(Ring * ptr);
int canSeekms(unsigned int ms); int canSeekms(unsigned int ms);
@ -411,9 +433,13 @@ namespace DTSC {
std::map<int, std::set<livePos> > keyframes; std::map<int, std::set<livePos> > keyframes;
virtual void addPacket(JSON::Value & newPack); virtual void addPacket(JSON::Value & newPack);
virtual void addMeta(JSON::Value & newMeta); virtual void addMeta(JSON::Value & newMeta);
void recordPacket(JSON::Value & packet); /*LTS*/
datatype datapointertype; datatype datapointertype;
unsigned int buffercount; unsigned int buffercount;
unsigned int buffertime; unsigned int buffertime;
std::string recordPath; /*LTS*/
File * recordFile; /*LTS*/
bool headerRecorded; /*LTS*/
std::map<unsigned int, std::string> trackMapping; std::map<unsigned int, std::string> trackMapping;
virtual void deletionCallback(livePos deleting); virtual void deletionCallback(livePos deleting);
}; };

View file

@ -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 ///\brief Returns the payloadsize of a part
long Part::getSize() { long Part::getSize() {
@ -978,6 +1008,12 @@ namespace DTSC {
Part * tmp = (Part *)trackRef["parts"].asStringRef().data(); Part * tmp = (Part *)trackRef["parts"].asStringRef().data();
parts = std::deque<Part>(tmp, tmp + (trackRef["parts"].asStringRef().size() / 9)); parts = std::deque<Part>(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<Ivec>(tmp, tmp + (trackRef["ivecs"].asString().size() / 8));
}
/*LTS-END*/
trackID = trackRef["trackid"].asInt(); trackID = trackRef["trackid"].asInt();
firstms = trackRef["firstms"].asInt(); firstms = trackRef["firstms"].asInt();
lastms = trackRef["lastms"].asInt(); lastms = trackRef["lastms"].asInt();
@ -1024,6 +1060,14 @@ namespace DTSC {
trackRef.getMember("parts").getString(tmp, tmplen); trackRef.getMember("parts").getString(tmp, tmplen);
parts = std::deque<Part>((Part *)tmp, ((Part *)tmp) + (tmplen / 9)); parts = std::deque<Part>((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>((Ivec *)tmp, ((Ivec *)tmp) + (tmplen / 8));
}
/*LTS-END*/
trackID = trackRef.getMember("trackid").asInt(); trackID = trackRef.getMember("trackid").asInt();
firstms = trackRef.getMember("firstms").asInt(); firstms = trackRef.getMember("firstms").asInt();
lastms = trackRef.getMember("lastms").asInt(); lastms = trackRef.getMember("lastms").asInt();
@ -1086,6 +1130,13 @@ namespace DTSC {
} else { } else {
newKey.setBpos(0); 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); keys.push_back(newKey);
keySizes.push_back(0); keySizes.push_back(0);
firstms = keys[0].getTime(); firstms = keys[0].getTime();
@ -1376,6 +1427,7 @@ namespace DTSC {
result += 11 + (keySizes.size() * 4) + 4; result += 11 + (keySizes.size() * 4) + 4;
} }
result += parts.size() * 9; result += parts.size() * 9;
result += (ivecs.size() * 8) + 12; /*LTS*/
if (type == "audio") { if (type == "audio") {
result += 49; result += 49;
} else if (type == "video") { } else if (type == "video") {
@ -1433,6 +1485,13 @@ namespace DTSC {
for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) { for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) {
writePointer(p, it->getData(), 9); writePointer(p, it->getData(), 9);
} }
/*LTS-START*/
writePointer(p, "\000\005ivecs\002", 8);
writePointer(p, convertInt(ivecs.size() * 8), 4);
for (std::deque<Ivec>::iterator it = ivecs.begin(); it != ivecs.end(); it++) {
writePointer(p, it->getData(), 8);
}
/*LTS-END*/
writePointer(p, "\000\007trackid\001", 10); writePointer(p, "\000\007trackid\001", 10);
writePointer(p, convertLongLong(trackID), 8); writePointer(p, convertLongLong(trackID), 8);
if (missedFrags) { if (missedFrags) {
@ -1503,6 +1562,13 @@ namespace DTSC {
for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) { for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) {
conn.SendNow(it->getData(), 9); conn.SendNow(it->getData(), 9);
} }
/*LTS-START*/
conn.SendNow("\000\005ivecs\002", 8);
conn.SendNow(convertInt(ivecs.size() * 8), 4);
for (std::deque<Ivec>::iterator it = ivecs.begin(); it != ivecs.end(); it++) {
conn.SendNow(it->getData(), 8);
}
/*LTS-END*/
conn.SendNow("\000\007trackid\001", 10); conn.SendNow("\000\007trackid\001", 10);
conn.SendNow(convertLongLong(trackID), 8); conn.SendNow(convertLongLong(trackID), 8);
if (missedFrags) { if (missedFrags) {
@ -1643,6 +1709,14 @@ namespace DTSC {
tmp.append(it->getData(), 9); tmp.append(it->getData(), 9);
} }
result["parts"] = tmp; result["parts"] = tmp;
/*LTS-START*/
tmp = "";
tmp.reserve(ivecs.size() * 8);
for (std::deque<Ivec>::iterator it = ivecs.begin(); it != ivecs.end(); it++) {
tmp.append(it->getData(), 8);
}
result["ivecs"] = tmp;
/*LTS-END*/
result["trackid"] = trackID; result["trackid"] = trackID;
result["firstms"] = (long long)firstms; result["firstms"] = (long long)firstms;
result["lastms"] = (long long)lastms; result["lastms"] = (long long)lastms;

62
lib/encryption.cpp Normal file
View file

@ -0,0 +1,62 @@
#include "encryption.h"
#include "auth.h"
//#include <openssl/aes.h>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <cstdio>
#include <cstdio>
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;
}
}

8
lib/encryption.h Normal file
View file

@ -0,0 +1,8 @@
#include <string>
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);
}

View file

@ -4,7 +4,9 @@
#include "mp4.h" #include "mp4.h"
#include "mp4_adobe.h" #include "mp4_adobe.h"
#include "mp4_ms.h" #include "mp4_ms.h"
#include "mp4_dash.h"
#include "mp4_generic.h" #include "mp4_generic.h"
#include "mp4_encryption.h" // /*LTS*/
#include "json.h" #include "json.h"
#include "defines.h" #include "defines.h"
@ -104,6 +106,7 @@ namespace MP4 {
} }
} else if (size == 0) { } else if (size == 0) {
fseek(newData, 0, SEEK_END); fseek(newData, 0, SEEK_END);
return true;
} }
DONTEVEN_MSG("skipping size 0x%.8lX", size); DONTEVEN_MSG("skipping size 0x%.8lX", size);
if (fseek(newData, pos + size, SEEK_SET) == 0) { if (fseek(newData, pos + size, SEEK_SET) == 0) {
@ -132,6 +135,10 @@ namespace MP4 {
return false; 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); fseek(newData, pos, SEEK_SET);
data = (char *)realloc(data, size); data = (char *)realloc(data, size);
data_size = size; data_size = size;
@ -160,6 +167,9 @@ namespace MP4 {
return false; return false;
} }
} }
if (size == 0){
size = newData.size();
}
if (newData.size() >= size) { if (newData.size() >= size) {
data = (char *)realloc(data, size); data = (char *)realloc(data, size);
data_size = size; data_size = size;
@ -243,6 +253,9 @@ namespace MP4 {
case 0x74666864: case 0x74666864:
return ((TFHD *)this)->toPrettyString(indent); return ((TFHD *)this)->toPrettyString(indent);
break; break;
case 0x68766343:
return ((HVCC *)this)->toPrettyString(indent);
break;
case 0x61766343: case 0x61766343:
return ((AVCC *)this)->toPrettyString(indent); return ((AVCC *)this)->toPrettyString(indent);
break; break;
@ -252,6 +265,9 @@ namespace MP4 {
case 0x66747970: case 0x66747970:
return ((FTYP *)this)->toPrettyString(indent); return ((FTYP *)this)->toPrettyString(indent);
break; break;
case 0x73747970:
return ((STYP*)this)->toPrettyString(indent);
break;
case 0x6D6F6F76: case 0x6D6F6F76:
return ((MOOV *)this)->toPrettyString(indent); return ((MOOV *)this)->toPrettyString(indent);
break; break;
@ -344,11 +360,18 @@ namespace MP4 {
break; break;
case 0x6D703461://mp4a case 0x6D703461://mp4a
case 0x656E6361://enca case 0x656E6361://enca
case 0x61632D33://ac-3
return ((MP4A *)this)->toPrettyString(indent); return ((MP4A *)this)->toPrettyString(indent);
break; break;
case 0x64616333:
return ((DAC3 *)this)->toPrettyString(indent);
break;
case 0x61616320: case 0x61616320:
return ((AAC *)this)->toPrettyString(indent); return ((AAC *)this)->toPrettyString(indent);
break; break;
case 0x68657631:
return ((HEV1 *)this)->toPrettyString(indent);
break;
case 0x61766331: case 0x61766331:
return ((AVC1 *)this)->toPrettyString(indent); return ((AVC1 *)this)->toPrettyString(indent);
break; break;
@ -356,6 +379,15 @@ namespace MP4 {
case 0x656E6376://encv case 0x656E6376://encv
return ((H264 *)this)->toPrettyString(indent); return ((H264 *)this)->toPrettyString(indent);
break; 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: case 0x65647473:
return ((EDTS *)this)->toPrettyString(indent); return ((EDTS *)this)->toPrettyString(indent);
break; break;
@ -377,12 +409,36 @@ namespace MP4 {
case 0x75756964: case 0x75756964:
return ((UUID *)this)->toPrettyString(indent); return ((UUID *)this)->toPrettyString(indent);
break; 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: default:
break; 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? /// \todo Implement hexdump for unimplemented boxes?
return retval; return retval.str();
} }
/// Sets the 8 bits integer at the given index. /// Sets the 8 bits integer at the given index.
@ -804,4 +860,5 @@ namespace MP4 {
} }
return r.str(); return r.str();
} }
} }

240
lib/mp4_dash.cpp Normal file
View file

@ -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();
}
}

73
lib/mp4_dash.h Normal file
View file

@ -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);
};
}

456
lib/mp4_encryption.cpp Normal file
View file

@ -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<UUID_SampleEncryption_Sample_Entry>::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();
}
}

104
lib/mp4_encryption.h Normal file
View file

@ -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<UUID_SampleEncryption_Sample_Entry> 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);
};
}

View file

@ -639,6 +639,192 @@ namespace MP4 {
memcpy((char *)payload(), (char *)newPayload.c_str(), newPayload.size()); 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<HVCCArrayEntry> HVCC::getArrays(){
std::deque<HVCCArrayEntry> 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<HVCCArrayEntry> 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<HVCCArrayEntry> 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(){ Descriptor::Descriptor(){
data = (char*)malloc(2); data = (char*)malloc(2);
data[0] = 0; data[0] = 0;
@ -967,19 +1153,85 @@ namespace MP4 {
return r.str(); return r.str();
} }
FTYP::FTYP() { DAC3::DAC3(){
memcpy(data + 4, "ftyp", 4); memcpy(data + 4, "dac3", 4);
setMajorBrand("mp41"); setInt24(0,0);
setMinorVersion("Mist");
setCompatibleBrands("isom", 0);
setCompatibleBrands("iso2", 1);
setCompatibleBrands("avc1", 2);
setCompatibleBrands("mp41", 3);
} }
void FTYP::setMajorBrand(const char * newMajorBrand) { char DAC3::getSampleRateCode(){
if (payloadOffset + 3 >= boxedSize()) { return getInt8(0) >> 6;
if (!reserve(payloadOffset, 0, 4)) { }
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; return;
} }
} }
@ -996,11 +1248,16 @@ namespace MP4 {
return; return;
} }
} }
memset(data + payloadOffset + 4, 0, 4);
memcpy(data + payloadOffset + 4, newMinorVersion, 4); memcpy(data + payloadOffset + 4, newMinorVersion, 4);
} }
std::string FTYP::getMinorVersion() { std::string FTYP::getMinorVersion(){
return std::string(data + payloadOffset + 4, 4); 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() { size_t FTYP::getCompatibleBrandsCount() {
@ -1035,6 +1292,22 @@ namespace MP4 {
return r.str(); 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() { MOOV::MOOV() {
memcpy(data + 4, "moov", 4); memcpy(data + 4, "moov", 4);
} }
@ -1045,48 +1318,53 @@ namespace MP4 {
TREX::TREX() { TREX::TREX() {
memcpy(data + 4, "trex", 4); memcpy(data + 4, "trex", 4);
setTrackID(0);
setDefaultSampleDescriptionIndex(1);
setDefaultSampleDuration(0);
setDefaultSampleSize(0);
setDefaultSampleFlags(0);
} }
void TREX::setTrackID(uint32_t newTrackID) { void TREX::setTrackID(uint32_t newTrackID){
setInt32(newTrackID, 0); setInt32(newTrackID, 4);
} }
uint32_t TREX::getTrackID() { uint32_t TREX::getTrackID(){
return getInt32(0);
}
void TREX::setDefaultSampleDescriptionIndex(uint32_t newDefaultSampleDescriptionIndex) {
setInt32(newDefaultSampleDescriptionIndex, 4);
}
uint32_t TREX::getDefaultSampleDescriptionIndex() {
return getInt32(4); return getInt32(4);
} }
void TREX::setDefaultSampleDuration(uint32_t newDefaultSampleDuration) { void TREX::setDefaultSampleDescriptionIndex(uint32_t newDefaultSampleDescriptionIndex){
setInt32(newDefaultSampleDuration, 8); setInt32(newDefaultSampleDescriptionIndex,8);
} }
uint32_t TREX::getDefaultSampleDuration() { uint32_t TREX::getDefaultSampleDescriptionIndex(){
return getInt32(8); return getInt32(8);
} }
void TREX::setDefaultSampleSize(uint32_t newDefaultSampleSize) { void TREX::setDefaultSampleDuration(uint32_t newDefaultSampleDuration){
setInt32(newDefaultSampleSize, 12); setInt32(newDefaultSampleDuration,12);
} }
uint32_t TREX::getDefaultSampleSize() { uint32_t TREX::getDefaultSampleDuration(){
return getInt32(12); return getInt32(12);
} }
void TREX::setDefaultSampleFlags(uint32_t newDefaultSampleFlags) { void TREX::setDefaultSampleSize(uint32_t newDefaultSampleSize){
setInt32(newDefaultSampleFlags, 16); setInt32(newDefaultSampleSize,16);
} }
uint32_t TREX::getDefaultSampleFlags() { uint32_t TREX::getDefaultSampleSize(){
return getInt32(16); 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::string TREX::toPrettyString(uint32_t indent) {
std::stringstream r; std::stringstream r;
r << std::string(indent, ' ') << "[trex] Track Extends (" << boxedSize() << ")" << std::endl; r << std::string(indent, ' ') << "[trex] Track Extends (" << boxedSize() << ")" << std::endl;
@ -1576,28 +1854,28 @@ namespace MP4 {
int32_t MVHD::getMatrix(size_t index) { int32_t MVHD::getMatrix(size_t index) {
int offset = 0; int offset = 0;
if (getVersion() == 0) { if (getVersion() == 0){
offset = 24 + 2 + 10; offset = 36;
} else { }else{
offset = 36 + 2 + 10; offset = 48;
} }
return getInt32(offset + index * 4); return getInt32(offset + index * 4);
} }
//24 bytes of pre-defined in between //24 bytes of pre-defined in between
void MVHD::setTrackID(uint32_t newTrackID) { void MVHD::setTrackID(uint32_t newTrackID){
if (getVersion() == 0) { if (getVersion() == 0){
setInt32(newTrackID, 86); setInt32(newTrackID, 96);
} else { }else{
setInt32(newTrackID, 98); setInt32(newTrackID, 108);
} }
} }
uint32_t MVHD::getTrackID() { uint32_t MVHD::getTrackID(){
if (getVersion() == 0) { if (getVersion() == 0){
return getInt32(86); return getInt32(96);
} else { }else{
return getInt32(98); return getInt32(108);
} }
} }
@ -2775,6 +3053,13 @@ namespace MP4 {
return getBox(28); 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::string AudioSampleEntry::toPrettyAudioString(uint32_t indent, std::string name) {
std::stringstream r; std::stringstream r;
r << std::string(indent, ' ') << name << " (" << boxedSize() << ")" << std::endl; 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, ' ') << "PreDefined: " << getPreDefined() << std::endl;
r << std::string(indent + 1, ' ') << "SampleRate: " << getSampleRate() << std::endl; r << std::string(indent + 1, ' ') << "SampleRate: " << getSampleRate() << std::endl;
r << getCodecBox().toPrettyString(indent + 1) << std::endl; r << getCodecBox().toPrettyString(indent + 1) << std::endl;
/*LTS-START*/
if (isType("enca")) {
r << getSINFBox().toPrettyString(indent + 1);
}
/*LTS-END*/
return r.str(); return r.str();
} }
@ -2803,6 +3093,14 @@ namespace MP4 {
return toPrettyAudioString(indent, "[aac ] Advanced Audio Codec"); 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() { AVC1::AVC1() {
memcpy(data + 4, "avc1", 4); memcpy(data + 4, "avc1", 4);
} }
@ -2819,6 +3117,34 @@ namespace MP4 {
return toPrettyVisualString(indent, "[h264] H.264/MPEG-4 AVC"); 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) { STSD::STSD(char v, uint32_t f) {
memcpy(data + 4, "stsd", 4); memcpy(data + 4, "stsd", 4);
setVersion(v); setVersion(v);
@ -2881,6 +3207,14 @@ namespace MP4 {
return r.str(); return r.str();
} }
GMHD::GMHD() {
memcpy(data + 4, "gmhd", 4);
}
TREF::TREF() {
memcpy(data + 4, "tref", 4);
}
EDTS::EDTS() { EDTS::EDTS() {
memcpy(data + 4, "edts", 4); memcpy(data + 4, "edts", 4);
} }
@ -3022,3 +3356,4 @@ namespace MP4 {
return r.str(); return r.str();
} }
} }

View file

@ -1,3 +1,4 @@
#pragma once
#include "mp4.h" #include "mp4.h"
namespace MP4 { namespace MP4 {
@ -122,6 +123,56 @@ namespace MP4 {
std::string toPrettyString(uint32_t indent = 0); std::string toPrettyString(uint32_t indent = 0);
}; };
struct HVCCArrayEntry {
char arrayCompleteness;
char nalUnitType;
std::deque<std::string> 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<HVCCArrayEntry> getArrays();
std::string asAnnexB();
void setPayload(std::string newPayload);
std::string toPrettyString(uint32_t indent = 0);
};
class Descriptor{ class Descriptor{
public: public:
Descriptor(); Descriptor();
@ -182,9 +233,27 @@ namespace MP4 {
std::string toPrettyString(uint32_t indent = 0); 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 { class FTYP: public Box {
public: public:
FTYP(); FTYP(bool fillDefaults = true);
void setMajorBrand(const char * newMajorBrand); void setMajorBrand(const char * newMajorBrand);
std::string getMajorBrand(); std::string getMajorBrand();
void setMinorVersion(const char * newMinorVersion); void setMinorVersion(const char * newMinorVersion);
@ -195,6 +264,12 @@ namespace MP4 {
std::string toPrettyString(uint32_t indent = 0); 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 { class MOOV: public containerBox {
public: public:
MOOV(); MOOV();
@ -205,7 +280,7 @@ namespace MP4 {
MVEX(); MVEX();
}; };
class TREX: public Box { class TREX: public fullBox {
public: public:
TREX(); TREX();
void setTrackID(uint32_t newTrackID); void setTrackID(uint32_t newTrackID);
@ -600,6 +675,7 @@ namespace MP4 {
uint32_t getSampleRate(); uint32_t getSampleRate();
void setCodecBox(Box & newBox); void setCodecBox(Box & newBox);
Box & getCodecBox(); Box & getCodecBox();
Box & getSINFBox(); /*LTS*/
std::string toPrettyAudioString(uint32_t indent = 0, std::string name = ""); std::string toPrettyAudioString(uint32_t indent = 0, std::string name = "");
}; };
@ -615,6 +691,12 @@ namespace MP4 {
std::string toPrettyString(uint32_t indent = 0); std::string toPrettyString(uint32_t indent = 0);
}; };
class HEV1: public VisualSampleEntry {
public:
HEV1();
std::string toPrettyString(uint32_t indent = 0);
};
class AVC1: public VisualSampleEntry { class AVC1: public VisualSampleEntry {
public: public:
AVC1(); AVC1();
@ -627,6 +709,16 @@ namespace MP4 {
std::string toPrettyString(uint32_t indent = 0); 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 { class STSD: public fullBox {
public: public:
STSD(char v = 1, uint32_t f = 0); STSD(char v = 1, uint32_t f = 0);
@ -637,6 +729,16 @@ namespace MP4 {
std::string toPrettyString(uint32_t indent = 0); std::string toPrettyString(uint32_t indent = 0);
}; };
class GMHD: public containerBox {
public:
GMHD();
};
class TREF: public containerBox {
public:
TREF();
};
class EDTS: public containerBox { class EDTS: public containerBox {
public: public:
EDTS(); EDTS();
@ -677,3 +779,4 @@ namespace MP4 {
std::string toPrettyString(uint32_t indent = 0); std::string toPrettyString(uint32_t indent = 0);
}; };
} }

View file

@ -1,4 +1,5 @@
#include "mp4_ms.h" #include "mp4_ms.h"
#include "mp4_encryption.h" /*LTS*/
namespace MP4 { namespace MP4 {
@ -127,6 +128,17 @@ namespace MP4 {
if (UUID == "d4807ef2-ca39-4695-8e54-26cb9e46a79f") { if (UUID == "d4807ef2-ca39-4695-8e54-26cb9e46a79f") {
return ((UUID_TrackFragmentReference *)this)->toPrettyString(indent); 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; std::stringstream r;
r << std::string(indent, ' ') << "[uuid] Extension box (" << boxedSize() << ")" << std::endl; r << std::string(indent, ' ') << "[uuid] Extension box (" << boxedSize() << ")" << std::endl;
r << std::string(indent + 1, ' ') << "UUID: " << UUID << std::endl; r << std::string(indent + 1, ' ') << "UUID: " << UUID << std::endl;

239
lib/rtp.cpp Normal file
View file

@ -0,0 +1,239 @@
#include <arpa/inet.h>
#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;
}
}

62
lib/rtp.h Normal file
View file

@ -0,0 +1,62 @@
#pragma once
#include <string>
#include <vector>
#include <set>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <stdint.h>
#include <sstream>
#include <deque>
#include <algorithm>
#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; ///<The actual RTP packet that is being sent
unsigned int datalen; ///<Size of rtp packet
int sentPackets;
int sentBytes;//Because ugly is beautiful
inline void htobll(char * p, long long val);
public:
static double startRTCP;
unsigned int getHsize() const;
unsigned int getVersion() const;
unsigned int getPadding() const;
unsigned int getExtension() const;
unsigned int getContribCount() const;
unsigned int getMarker() const;
unsigned int getPayloadType() const;
unsigned int getSequence() const;
unsigned int getTimeStamp() const;
void setSequence(unsigned int seq);
unsigned int getSSRC() const;
void setSSRC(unsigned long ssrc);
void setTimestamp(unsigned int t);
void increaseSequence();
void sendH264(void * socket, void callBack(void *, char *, unsigned int, unsigned int), const char * payload, unsigned int payloadlen, unsigned int channel);
void sendAAC(void * socket, void callBack(void *, char *, unsigned int, unsigned int), const char * payload, unsigned int payloadlen, unsigned int channel);
void sendRaw(void * socket, void callBack(void *, char *, unsigned int, unsigned int), const char * payload, unsigned int payloadlen, unsigned int channel);
void sendRTCP(long long & connectedAt, void * socket, unsigned int tid, DTSC::Meta & metadata, void callBack(void *, char *, unsigned int, unsigned int));
Packet();
Packet(unsigned int pt, unsigned int seq, unsigned int ts, unsigned int ssr, unsigned int csrcCount = 0);
Packet(const Packet & o);
void operator=(const Packet & o);
~Packet();
Packet(const char * dat, unsigned int len);
char * getData();
};
}

View file

@ -80,6 +80,12 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
configLock.wait(); configLock.wait();
DTSC::Scan config = DTSC::Scan(mistConfOut.mapped, mistConfOut.len); DTSC::Scan config = DTSC::Scan(mistConfOut.mapped, mistConfOut.len);
/*LTS-START*/
if (config.getMember("hardlimit_active")) {
return false;
}
/*LTS-END*/
sanitizeName(streamname); sanitizeName(streamname);
std::string smp = streamname.substr(0, streamname.find_first_of("+ ")); std::string smp = streamname.substr(0, streamname.find_first_of("+ "));
//check if smp (everything before + or space) exists //check if smp (everything before + or space) exists
@ -90,6 +96,13 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
return false; return false;
} }
/*LTS-START*/
if (stream_cfg.getMember("hardlimit_active")) {
return false;
}
/*LTS-END*/
//If starting without filename parameter, check if the stream is already active. //If starting without filename parameter, check if the stream is already active.
//If yes, don't activate again to prevent duplicate inputs. //If yes, don't activate again to prevent duplicate inputs.
//It's still possible a duplicate starts anyway, this is caught in the inputs initializer. //It's still possible a duplicate starts anyway, this is caught in the inputs initializer.

View file

@ -157,14 +157,14 @@ namespace tthread {
// Get thread startup information // Get thread startup information
_thread_start_info * ti = (_thread_start_info *) aArg; _thread_start_info * ti = (_thread_start_info *) aArg;
try { //try {
// Call the actual client thread function // Call the actual client thread function
ti->mFunction(ti->mArg); ti->mFunction(ti->mArg);
} catch (...) { //} catch (...) {
// Uncaught exceptions will terminate the application (default behavior // Uncaught exceptions will terminate the application (default behavior
// according to C++11) // according to C++11)
std::terminate(); //std::terminate();
} //}
// The thread is no longer executing // The thread is no longer executing
if (ti->mThread) { if (ti->mThread) {

View file

@ -1022,10 +1022,14 @@ namespace TS {
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
if (myMeta.tracks[*it].codec == "H264"){ if (myMeta.tracks[*it].codec == "H264"){
PMT.setStreamType(0x1B,id); PMT.setStreamType(0x1B,id);
}else if (myMeta.tracks[*it].codec == "HEVC"){
PMT.setStreamType(0x06,id);
}else if (myMeta.tracks[*it].codec == "AAC"){ }else if (myMeta.tracks[*it].codec == "AAC"){
PMT.setStreamType(0x0F,id); PMT.setStreamType(0x0F,id);
}else if (myMeta.tracks[*it].codec == "MP3"){ }else if (myMeta.tracks[*it].codec == "MP3"){
PMT.setStreamType(0x03,id); PMT.setStreamType(0x03,id);
}else if (myMeta.tracks[*it].codec == "AC3"){
PMT.setStreamType(0x81,id);
} }
PMT.setElementaryPID(0x100 + (*it) - 1, id); PMT.setElementaryPID(0x100 + (*it) - 1, id);
PMT.setESInfoLength(0,id); PMT.setESInfoLength(0,id);

View file

@ -96,6 +96,7 @@ namespace Info {
fileSpecs["tracks"][trackIt->first].removeMember("keys"); fileSpecs["tracks"][trackIt->first].removeMember("keys");
fileSpecs["tracks"][trackIt->first].removeMember("keysizes"); fileSpecs["tracks"][trackIt->first].removeMember("keysizes");
fileSpecs["tracks"][trackIt->first].removeMember("parts"); fileSpecs["tracks"][trackIt->first].removeMember("parts");
fileSpecs["tracks"][trackIt->first].removeMember("ivecs");/*LTS*/
} }
} }
printf( "%s", fileSpecs.toString().c_str() ); printf( "%s", fileSpecs.toString().c_str() );

View file

@ -9,6 +9,7 @@
#include <string.h> #include <string.h>
#include <mist/mp4.h> #include <mist/mp4.h>
#include <mist/config.h> #include <mist/config.h>
#include <mist/defines.h>
///\brief Holds everything unique to the analysers. ///\brief Holds everything unique to the analysers.
namespace Analysers { namespace Analysers {
@ -25,9 +26,15 @@ namespace Analysers {
mp4Buffer.erase(mp4Buffer.size() - 1, 1); mp4Buffer.erase(mp4Buffer.size() - 1, 1);
MP4::Box mp4Data; MP4::Box mp4Data;
int dataSize = mp4Buffer.size();
int curPos = 0;
while (mp4Data.read(mp4Buffer)){ while (mp4Data.read(mp4Buffer)){
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
std::cerr << mp4Data.toPrettyString(0) << std::endl; 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; return 0;
} }
} }

View file

@ -0,0 +1,209 @@
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <string.h>
#include <vector>
#include <sstream>
#include <mist/socket.h>
#include <mist/config.h>
#include <mist/rtp.h>
#include <mist/http_parser.h>
//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<std::string> tracks;
std::vector<Socket::UDPConnection> 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()<<std::endl;
}else if(step == 4){
std::cerr << "Play!!!1" << std::endl;
HTTP_S.method = "PLAY";
HTTP_S.url = "rtsp://localhost/steers";
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
HTTP_S.SetHeader("Range","npt=0.000-");
HTTP_S.SendRequest(conn);
step++;
std::cerr << "step for play.." << step << std::endl;
}
if (conn.Received().size() || conn.spool()){
if (HTTP_R.Read(conn)){
if(step == 1){
std::cerr << "recvd desc" << std::endl;
for(size_t ml = HTTP_R.body.find("a=control:",HTTP_R.body.find("m=")); ml != std::string::npos; ml = HTTP_R.body.find("a=control:",ml+1)){
std::cerr << "found trekk" << std::endl;
tracks.push_back(HTTP_R.body.substr(ml+10,HTTP_R.body.find_first_of("\r\n",ml)-(ml+10)));
connections.push_back(Socket::UDPConnection());
}
for(unsigned int x = 0; x < connections.size();x++){
connections[x].SetDestination("127.0.0.1",666);
connections[x].bind(20000+2*x);
connections[x].setBlocking(true);
}
step++;
}else if(step == 3){
std::cerr << "recvd setup" << std::endl;
std::cerr << "trackIt: " << trackIt << " size " << tracks.size() << std::endl;
if(trackIt < tracks.size())
step--;
else
step++;
std::cerr << HTTP_R.GetHeader("Transport");
}
HTTP_R.Clean();
}
}//!
if(step == 5){
for(unsigned int cx = 0; cx < connections.size(); cx++){
// std::cerr <<"PLAY MF" << std::endl;
if(connections[cx].Receive()){
RTP::Packet* pakketje = new RTP::Packet(connections[cx].data, connections[cx].data_len);
/*std::cout << "Version = " << pakketje->getVersion() << std::endl;
std::cout << "Padding = " << pakketje->getPadding() << std::endl;
std::cout << "Extension = " << pakketje->getExtension() << std::endl;
std::cout << "Contributing sources = " << pakketje->getContribCount() << std::endl;
std::cout << "Marker = " << pakketje->getMarker() << std::endl;
std::cout << "Payload Type = " << pakketje->getPayloadType() << std::endl;
std::cout << "Sequence = " << pakketje->getSequence() << std::endl;
std::cout << "Timestamp = " << pakketje->getTimeStamp() << std::endl;
std::cout << "SSRC = " << pakketje->getSSRC() << std::endl;
std::cout << "datalen: " << connections[cx].data_len << std::endl;
std::cout << "payload:" << std::endl;*/
if(pakketje->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 <<std::setw(2) << std::setfill('0') << (int)connections[cx].data[i]<< std::dec;
}
std::cout << std::endl<<std::endl;
}
delete pakketje;
}
}
}
}
return 666;
}
}
int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
conf.parseArgs(argc, argv);
return Analysers::analyseRTP();
}

View file

@ -0,0 +1,197 @@
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include <string.h>
#include <vector>
#include <mist/config.h>
#include <mist/rtp.h>
#include <mist/socket.h>
#include <mist/http_parser.h>
#include <sstream>
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<std::string> 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<std::string>::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] <<"-" <<setports[setupsSent]+1 ;
HTTP_R.SetHeader("Transport",setup.str() );
std:: cout << setup.str()<<std::endl;
setup.str(std::string());
setup.clear();
HTTP_R.SetHeader("CSeq",CSeq);
CSeq++;
if(sessionID != ""){
HTTP_R.SetHeader("Session",sessionID);
}
HTTP_R.SetHeader("User-Agent","mistANALyser");
HTTP_R.SendRequest(conn);
setupsSent ++;
}
while(setupsSent == setupsRecvd+1){
//lets Assume we assume we always receive a response
if ( (conn.Received().size() || conn.spool() )){
if(HTTP_S.Read(conn)){
std::cout << "recv setup" << std::endl;
std::cout << HTTP_S.BuildResponse(HTTP_S.method,HTTP_S.url);
optionsRecvd = true;
sessionID = HTTP_S.GetHeader("Session");
setupsRecvd++;
}
}
}
//set up all parameters, and then after the for loop we have to listen to setups and all. sent if both are equal, and recv if one is sent
}
setupComplete = true;
}
if(setupComplete && !playSent){
//time to play
HTTP_S.Clean();
HTTP_R.protocol="RTSP/1.0";
HTTP_R.method = "PLAY";
HTTP_R.url = rtspUrl;
HTTP_R.SetHeader("CSeq",CSeq);
CSeq++;
HTTP_R.SetHeader("User-Agent","mistANALyser");
HTTP_R.SetHeader("Session",sessionID);
HTTP_R.SendRequest(conn);
playSent = true;
std::cout << "sent play" << std::endl;
char buffer[2000];
while(!connectors[0].iread((void*)buffer,2000)) {
std::cout << "buffer";
}
std::cout <<"buffer is not empty" << std::endl;
}
//streams set up
//time to read some packets
if(descRecvd){
conn.close();
}
}
conn.close();*/
return 0;
}
}
int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
conf.addOption("url",JSON::fromString("{\"arg\":\"string\",\"short\":\"u\",\"long\":\"url\",\"help\":\"URL To get.\", \"default\":\"rtsp://localhost/s1k\"}"));
conf.parseArgs(argc, argv);
return RtspRtp::analyseRtspRtp(conf.getString("url"));
}

View file

@ -0,0 +1,84 @@
/// \file stats_analyser.cpp
/// Will emulate a given amount of clients in the statistics.
#include <stdlib.h>
#include <mist/dtsc.h>
#include <mist/json.h>
#include <mist/shared_memory.h>
#include <mist/config.h>
#include <mist/defines.h>
/// 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;
}

189
src/analysers/ts_analyser.cpp Executable file
View file

@ -0,0 +1,189 @@
#include <fcntl.h>
#include <iostream>
#include <map>
#include <iomanip>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <string.h>
#include <fstream>
#include <unistd.h>
#include <sstream>
#include <signal.h>
#include <mist/ts_packet.h>
#include <mist/config.h>
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<d.size(); ++i){
if ((i < d.size() - 4) && d[i] == 0 && d[i+1] == 0 && d[i+2] == 0 && d[i+3] == 1){res << std::endl; counter = 0;}
res << std::hex << std::setw(2) << std::setfill('0') << (int)(d[i]&0xff) << " ";
counter++;
if ((counter) % 32 == 31){res << std::endl;}
}
res << std::endl;
}
return res.str();
}
/// Debugging tool for TS data.
/// Expects TS data through stdin, outputs human-readable information to stderr.
/// \return The return code of the analyser.
int analyseTS(bool validate, bool analyse, int detailLevel){
std::map<unsigned long long, std::string> 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<unsigned long long, std::string>::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"));
}

View file

@ -46,6 +46,11 @@
#include "controller_capabilities.h" #include "controller_capabilities.h"
#include "controller_connectors.h" #include "controller_connectors.h"
#include "controller_statistics.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" #include "controller_api.h"
#ifndef COMPILED_USERNAME #ifndef COMPILED_USERNAME
@ -88,7 +93,19 @@ void createAccount (std::string account){
/// Status monitoring thread. /// Status monitoring thread.
/// Will check outputs, inputs and converters every five seconds /// Will check outputs, inputs and converters every five seconds
void statusMonitor(void * np){ void statusMonitor(void * np){
#ifdef UPDATER
unsigned long updatechecker = Util::epoch(); /*LTS*/
#endif
while (Controller::conf.is_active){ 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 //this scope prevents the configMutex from being locked constantly
{ {
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex); tthread::lock_guard<tthread::mutex> 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("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("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.\"}")); 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); Controller::conf.parseArgs(argc, argv);
if(Controller::conf.getString("logfile")!= ""){ if(Controller::conf.getString("logfile")!= ""){
//open logfile, dup stdout to logfile //open logfile, dup stdout to logfile
@ -246,14 +269,29 @@ int main(int argc, char ** argv){
Controller::Log("CONF", "Controller started"); Controller::Log("CONF", "Controller started");
Controller::conf.activate();//activate early, so threads aren't killed. 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 //start stats thread
tthread::thread statsThread(Controller::SharedMemStats, &Controller::conf); tthread::thread statsThread(Controller::SharedMemStats, &Controller::conf);
//start monitoring thread //start monitoring thread
tthread::thread monitorThread(statusMonitor, 0); tthread::thread monitorThread(statusMonitor, 0);
//start monitoring thread /*LTS*/
tthread::thread uplinkThread(Controller::uplinkConnection, 0);/*LTS*/
//start main loop //start main loop
Controller::conf.serveThreadedSocket(Controller::handleAPIConnection); Controller::conf.serveThreadedSocket(Controller::handleAPIConnection);
//print shutdown reason //print shutdown reason
/*LTS-START*/
if (Controller::restarting){
Controller::Log("CONF", "Controller restarting for update");
}
/*LTS-END*/
if (!Controller::conf.is_active){ if (!Controller::conf.is_active){
Controller::Log("CONF", "Controller shutting down because of user request (received shutdown signal)"); Controller::Log("CONF", "Controller shutting down because of user request (received shutdown signal)");
}else{ }else{
@ -263,6 +301,7 @@ int main(int argc, char ** argv){
//join all joinable threads //join all joinable threads
statsThread.join(); statsThread.join();
monitorThread.join(); monitorThread.join();
uplinkThread.join();/*LTS*/
//give everything some time to print messages //give everything some time to print messages
Util::wait(100); Util::wait(100);
//close stderr to make the stderr reading thread exit //close stderr to make the stderr reading thread exit
@ -282,5 +321,12 @@ int main(int argc, char ** argv){
//stop all child processes //stop all child processes
Util::Procs::StopAll(); Util::Procs::StopAll();
std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl; 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; return 0;
} }

View file

@ -11,6 +11,10 @@
#include "controller_connectors.h" #include "controller_connectors.h"
#include "controller_capabilities.h" #include "controller_capabilities.h"
#include "controller_statistics.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. ///\brief Check the submitted configuration and handle things accordingly.
///\param in The new configuration. ///\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! /// 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. /// 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){ 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); time_t Time = time(0);
tm * TimeInfo = localtime( &Time); tm * TimeInfo = localtime( &Time);
std::stringstream Date; std::stringstream Date;
@ -191,6 +199,148 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
if (Request.isMember("streams")){ if (Request.isMember("streams")){
Controller::CheckStreams(Request["streams"], Controller::Storage["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")){ if (Request.isMember("capabilities")){
Controller::checkCapable(capabilities); Controller::checkCapable(capabilities);
Response["capabilities"] = capabilities; Response["capabilities"] = capabilities;
@ -248,6 +398,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
/// ] /// ]
/// ] /// ]
/// ~~~~~~~~~~~~~~~ /// ~~~~~~~~~~~~~~~
///
if(Request.isMember("browse")){ if(Request.isMember("browse")){
if(Request["browse"] == ""){ if(Request["browse"] == ""){
Request["browse"] = "."; Request["browse"] = ".";
@ -312,6 +463,28 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
} }
Response["ui_settings"] = Storage["ui_settings"]; 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 //sent current configuration, no matter if it was changed or not
Response["config"] = Controller::Storage["config"]; Response["config"] = Controller::Storage["config"];
Response["config"]["version"] = PACKAGE_VERSION "/" + Util::Config::libver + "/" RELEASE; 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"]); Controller::fillTotals(Request["totals"], Response["totals"]);
} }
} }
if (Request.isMember("active_streams")){
Controller::fillActive(Request["active_streams"], Response["active_streams"]);
}
Controller::writeConfig(); Controller::writeConfig();
@ -370,6 +546,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
Util::sleep(1000);//sleep a second to prevent bruteforcing Util::sleep(1000);//sleep a second to prevent bruteforcing
logins++; logins++;
} }
Controller::checkServerLimits(); /*LTS*/
}//config mutex lock }//config mutex lock
//send the response, either normally or through JSONP callback. //send the response, either normally or through JSONP callback.
std::string jsonp = ""; std::string jsonp = "";

View file

@ -0,0 +1,403 @@
#include "controller_limits.h"
#include "controller_statistics.h"
#include "controller_storage.h"
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
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<std::string, long long> strmUsers;
std::map<std::string, long long> strmBandw;
/*
if (curConns.size()){
for (std::map<unsigned long, statStorage>::iterator it = curConns.begin(); it != curConns.end(); it++){
if (it->second.log.size() < 2){continue;}
std::map<unsigned long long, statLog>::reverse_iterator statRef = it->second.log.rbegin();
std::map<unsigned long long, statLog>::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;
}
}

View file

@ -0,0 +1,21 @@
#pragma once
#include <mist/json.h>
#include <map>
#include <string>
/*LTS-START*/
#ifdef GEOIP
#include <GeoIP.h>
#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);
}

View file

@ -1,6 +1,11 @@
#include <cstdio> #include <cstdio>
#include <mist/config.h> #include <mist/config.h>
#include "controller_statistics.h" #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. // These are used to store "clients" field requests in a bitfield for speedup.
#define STAT_CLI_HOST 1 #define STAT_CLI_HOST 1
@ -23,6 +28,7 @@
std::map<Controller::sessIndex, Controller::statSession> Controller::sessions; ///< list of sessions that have statistics data available std::map<Controller::sessIndex, Controller::statSession> Controller::sessions; ///< list of sessions that have statistics data available
std::map<unsigned long, Controller::sessIndex> Controller::connToSession; ///< Map of socket IDs to session info. std::map<unsigned long, Controller::sessIndex> Controller::connToSession; ///< Map of socket IDs to session info.
bool Controller::killOnExit = KILL_ON_EXIT;
tthread::mutex Controller::statsMutex; tthread::mutex Controller::statsMutex;
Controller::sessIndex::sessIndex(std::string dhost, unsigned int dcrc, std::string dstreamName, std::string dconnector){ 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); 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 /// This function runs as a thread and roughly once per second retrieves
/// statistics from all connected clients, as well as wipes /// statistics from all connected clients, as well as wipes
@ -98,10 +108,19 @@ void Controller::SharedMemStats(void * config){
it->second.wipeOld(cutOffPoint); it->second.wipeOld(cutOffPoint);
} }
} }
Controller::checkServerLimits(); /*LTS*/
} }
Util::sleep(1000); Util::sleep(1000);
} }
DEBUG_MSG(DLVL_HIGH, "Stopping stats thread"); 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. /// 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); sessions[idx].finish(id);
connToSession.erase(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. /// 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. //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<std::string> streams;
//check all sessions
if (sessions.size()){
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
streams.insert(it->first.streamName);
}
}
//Good, now output what we found...
rep.null();
for (std::set<std::string>::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 { class totalsData {
public: public:
totalsData(){ totalsData(){

View file

@ -11,6 +11,9 @@
namespace Controller { namespace Controller {
extern bool killOnExit;
struct statLog { struct statLog {
long time; long time;
long lastSecond; long lastSecond;
@ -80,7 +83,9 @@ namespace Controller {
extern std::map<unsigned long, sessIndex> connToSession; extern std::map<unsigned long, sessIndex> connToSession;
extern tthread::mutex statsMutex; extern tthread::mutex statsMutex;
void parseStatistics(char * data, size_t len, unsigned int id); 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 fillClients(JSON::Value & req, JSON::Value & rep);
void fillActive(JSON::Value & req, JSON::Value & rep);
void fillTotals(JSON::Value & req, JSON::Value & rep); void fillTotals(JSON::Value & req, JSON::Value & rep);
void SharedMemStats(void * config); void SharedMemStats(void * config);
bool hasViewers(std::string streamName); bool hasViewers(std::string streamName);

View file

@ -9,6 +9,7 @@
#include "controller_capabilities.h" #include "controller_capabilities.h"
#include "controller_storage.h" #include "controller_storage.h"
#include "controller_statistics.h" #include "controller_statistics.h"
#include "controller_limits.h" /*LTS*/
#include <sys/stat.h> #include <sys/stat.h>
#include <map> #include <map>
@ -73,6 +74,7 @@ namespace Controller {
trackIt->second.removeMember("keys"); trackIt->second.removeMember("keys");
trackIt->second.removeMember("keysizes"); trackIt->second.removeMember("keysizes");
trackIt->second.removeMember("parts"); trackIt->second.removeMember("parts");
trackIt->second.removeMember("ivecs");/*LTS*/
} }
} }
} }
@ -82,7 +84,7 @@ namespace Controller {
//vod-style stream //vod-style stream
data.removeMember("error"); data.removeMember("error");
struct stat fileinfo; 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; data["error"] = "Stream offline: Not found: " + URL;
if (data["error"].asStringRef() != prevState){ if (data["error"].asStringRef() != prevState){
Log("BUFF", "Warning for VoD stream " + name + "! File not found: " + URL); 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()); DEBUG_MSG(DLVL_INSANE, "Invalid metadata (no tracks object) for stream %s - triggering reload", name.c_str());
getMeta = true; getMeta = true;
} }
if (*(URL.rbegin()) == '/'){
getMeta = false;
}
if (getMeta){ if (getMeta){
// if the file isn't dtsc and there's no dtsh file, run getStream on it // 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. // this guarantees that if the stream is playable, it now has a valid header.
@ -198,6 +203,7 @@ namespace Controller {
}else{ }else{
data["online"] = 1; data["online"] = 1;
} }
checkServerLimits(); /*LTS*/
return; return;
} }
/// \todo Implement ffmpeg pulling again? /// \todo Implement ffmpeg pulling again?
@ -231,6 +237,7 @@ namespace Controller {
} }
jit->second["online"] = 0; jit->second["online"] = 0;
} }
checkServerLimits(); /*LTS*/
}else{ }else{
// assume all is fine // assume all is fine
jit->second.removeMember("error"); jit->second.removeMember("error");
@ -242,7 +249,7 @@ namespace Controller {
jit->second["error"] = "No (valid) source connected "; jit->second["error"] = "No (valid) source connected ";
}else{ }else{
// for live streams, keep track of activity // 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<std::string, liveCheck> checker; static std::map<std::string, liveCheck> checker;
//check H264 tracks for optimality //check H264 tracks for optimality
if (jit->second.isMember("meta") && jit->second["meta"].isMember("tracks")){ 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].lastms = trIt->second["lastms"].asInt();
checker[jit->first].last_active = currTime; 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 // mark stream as offline if no activity for 5 seconds
@ -289,14 +300,8 @@ namespace Controller {
Log("STRM", std::string("Updated stream ") + jit->first); Log("STRM", std::string("Updated stream ") + jit->first);
} }
}else{ }else{
out[jit->first] = jit->second;
out[jit->first]["name"] = jit->first; 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); Log("STRM", std::string("New stream ") + jit->first);
} }
} }

View file

@ -0,0 +1,253 @@
/// \file controller_updater.cpp
/// Contains all code for the controller updater.
#include <fstream> //for files
#include <iostream> //for stdio
#include <unistd.h> //for unlink
#include <sys/stat.h> //for chmod
#include <stdlib.h> //for srand, rand
#include <time.h> //for time
#include <signal.h> //for raise
#include <mist/http_parser.h>
#include <mist/socket.h>
#include <mist/auth.h>
#include <mist/timing.h>
#include <mist/config.h>
#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

View file

@ -0,0 +1,21 @@
/// \file controller_updater.cpp
/// Contains all code for the controller updater.
#include <string>
#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

View file

@ -0,0 +1,138 @@
#include <stdlib.h>
#include <mist/auth.h>
#include <mist/dtsc.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/timing.h>
#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
}
}

View file

@ -0,0 +1,3 @@
namespace Controller {
void uplinkConnection(void * np);
}

281
src/input/input_av.cpp Normal file
View file

@ -0,0 +1,281 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/defines.h>
#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);
}
}

32
src/input/input_av.h Normal file
View file

@ -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 <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
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;

View file

@ -31,6 +31,44 @@ namespace Mist {
capa["optional"]["DVR"]["option"] = "--buffer"; capa["optional"]["DVR"]["option"] = "--buffer";
capa["optional"]["DVR"]["type"] = "uint"; capa["optional"]["DVR"]["type"] = "uint";
capa["optional"]["DVR"]["default"] = 50000LL; 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["source_match"] = "push://*";
capa["priority"] = 9ll; capa["priority"] = 9ll;
capa["desc"] = "Provides buffered live input"; capa["desc"] = "Provides buffered live input";
@ -41,6 +79,7 @@ namespace Mist {
singleton = this; singleton = this;
bufferTime = 0; bufferTime = 0;
cutTime = 0; cutTime = 0;
segmentSize = 0;
} }
inputBuffer::~inputBuffer() { inputBuffer::~inputBuffer() {
@ -48,7 +87,29 @@ namespace Mist {
if (myMeta.tracks.size()) { if (myMeta.tracks.size()) {
DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes"); DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes");
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
while (removeKey(it->first)) {} std::map<unsigned long, DTSCPageData> & 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<unsigned long, DTSCPageData>::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()); DEBUG_MSG(DLVL_HIGH, "Erasing key %d:%lu", tid, myMeta.tracks[tid].keys[0].getNumber());
//remove all parts of this key //remove all parts of this key
for (int i = 0; i < myMeta.tracks[tid].keys[0].getParts(); i++) { 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(); 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 //remove the key itself
myMeta.tracks[tid].keys.pop_front(); myMeta.tracks[tid].keys.pop_front();
myMeta.tracks[tid].keySizes.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) { 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 variable keeping track of the next temporary mapping to use for a track.
static int nextTempId = 1001; static int nextTempId = 1001;
//Get the counter of this user //Get the counter of this user
@ -331,6 +449,18 @@ namespace Mist {
std::string trackIdentifier = trackMeta.tracks.find(value)->second.getIdentifier(); std::string trackIdentifier = trackMeta.tracks.find(value)->second.getIdentifier();
DEBUG_MSG(DLVL_HIGH, "Attempting colision detection for track %s", trackIdentifier.c_str()); 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<unsigned int, DTSC::Track>::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 //Remove the "negotiate" status in either case
negotiatingTracks.erase(value); negotiatingTracks.erase(value);
@ -338,15 +468,49 @@ namespace Mist {
metaPages[value].master = true; metaPages[value].master = true;
metaPages.erase(value); 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") //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 //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) || 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) { 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 { } 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. //Register the new track as an active track.
@ -456,7 +620,8 @@ namespace Mist {
lastUpdated[tNum] = Util::bootSecs(); lastUpdated[tNum] = Util::bootSecs();
while (tmpPack) { while (tmpPack) {
//Update the metadata with this packet //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 //Set the first time when appropriate
if (pageData.firstTime == 0) { if (pageData.firstTime == 0) {
pageData.firstTime = tmpPack.getTime(); pageData.firstTime = tmpPack.getTime();
@ -469,6 +634,7 @@ namespace Mist {
} }
bool inputBuffer::setup() { bool inputBuffer::setup() {
lastReTime = Util::epoch(); /*LTS*/
std::string strName = config->getString("streamname"); std::string strName = config->getString("streamname");
Util::sanitizeName(strName); Util::sanitizeName(strName);
strName = strName.substr(0, (strName.find_first_of("+ "))); strName = strName.substr(0, (strName.find_first_of("+ ")));
@ -496,6 +662,74 @@ namespace Mist {
bufferTime = tmpNum; bufferTime = tmpNum;
} }
/*LTS-START*/
//if stream is configured and setting is present, use it, always
if (streamCfg && streamCfg.getMember("cut")) {
tmpNum = streamCfg.getMember("cut").asInt();
} else {
if (streamCfg) {
//otherwise, if stream is configured use the default
tmpNum = config->getOption("cut", true)[0u].asInt();
} else {
//if not, use the commandline argument
tmpNum = config->getOption("cut").asInt();
}
}
//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.post();
configLock.close(); configLock.close();
return true; return true;

View file

@ -1,3 +1,5 @@
#include <fstream>
#include "input.h" #include "input.h"
#include <mist/dtsc.h> #include <mist/dtsc.h>
#include <mist/shared_memory.h> #include <mist/shared_memory.h>
@ -10,6 +12,8 @@ namespace Mist {
private: private:
unsigned int bufferTime; unsigned int bufferTime;
unsigned int cutTime; unsigned int cutTime;
unsigned int segmentSize; /*LTS*/
unsigned int lastReTime; /*LTS*/
protected: protected:
//Private Functions //Private Functions
bool setup(); bool setup();
@ -31,6 +35,11 @@ namespace Mist {
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations; std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations;
std::map<unsigned long, char *> pushLocation; std::map<unsigned long, char *> pushLocation;
inputBuffer * singleton; inputBuffer * singleton;
std::string recName;/*LTS*/
DTSC::Meta recMeta;/*LTS*/
std::ofstream recFile;/*LTS*/
long long int recBpos;/*LTS*/
}; };
} }

View file

@ -0,0 +1,19 @@
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/defines.h>
#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;
}
}

14
src/input/input_folder.h Normal file
View file

@ -0,0 +1,14 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputFolder : public Input {
public:
inputFolder(Util::Config * cfg);
protected:
bool setup(){return false;};
bool readHeader(){return false;};
};
}
typedef Mist::inputFolder mistIn;

407
src/input/input_ismv.cpp Normal file
View file

@ -0,0 +1,407 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/defines.h>
#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<MP4::trunSampleInformation> trunSamples;
std::vector<std::string> initVecs;
std::string mdat;
unsigned int currOffset;
JSON::Value lastPack;
unsigned int lastBytePos = 0;
std::map<int, unsigned int> 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<unsigned long>::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<unsigned long>::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<MP4::trunSampleInformation> & trunSamples, std::vector<std::string> & 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);
}
}

51
src/input/input_ismv.h Normal file
View file

@ -0,0 +1,51 @@
#include "input.h"
#include <mist/dtsc.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
#include <mist/mp4_encryption.h>
#include <set>
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<MP4::trunSampleInformation> & trunSamples, std::vector<std::string> & initVecs, std::string & mdat);
void parseFragHeader(const unsigned int & trackId, const unsigned int & keyNum);
void readBox(const char * type, std::string & result);
std::set<seekPos> buffered;
std::map<int, int> lastKeyNum;
};
}
typedef Mist::inputISMV mistIn;

View file

@ -122,7 +122,7 @@ namespace Mist {
} }
} }
if (!offset){ 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; return;
} }
filePos += offset; filePos += offset;

632
src/input/input_mp4.cpp Normal file
View file

@ -0,0 +1,632 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/flv_tag.h>
#include <mist/defines.h>
#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<mp4PartBpos> 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<mp4PartBpos>::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<unsigned long>::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 = "";
}
}
}
}

102
src/input/input_mp4.h Normal file
View file

@ -0,0 +1,102 @@
#include "input.h"
#include <mist/dtsc.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
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<unsigned int, mp4TrackHeader> headerData;
std::set<mp4PartTime> curPositions;
//remember last seeked keyframe;
std::map <unsigned int, unsigned int> 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;

516
src/input/input_ts.cpp Executable file
View file

@ -0,0 +1,516 @@
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/flv_tag.h>
#include <mist/defines.h>
#include <mist/ts_packet.h>
#include <mist/mp4_generic.h>
#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<int> PATIds;
std::map<int, int> pidToType;
std::map<int, pesBuffer> 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<unsigned long>::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);
}
}
}
}

81
src/input/input_ts.h Executable file
View file

@ -0,0 +1,81 @@
#include "input.h"
#include <mist/nal.h>
#include <mist/dtsc.h>
#include <mist/ts_packet.h>
#include <string>
#include <set>
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;///<The input file with ts data
h264::NAL nal;///<Used to analyze raw h264 data
long long int lastPos;///<last position played in file
std::set<pesBuffer> playbackBuf;///Used for buffering playback items
std::map<int, int> firstTimes;
};
}
typedef Mist::inputTS mistIn;

View file

@ -0,0 +1,53 @@
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <semaphore.h>
#include INPUTTYPE
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/stream.h>
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;
}

View file

@ -1,6 +1,14 @@
#include OUTPUTTYPE #include OUTPUTTYPE
#include <mist/config.h> #include <mist/config.h>
#include <mist/socket.h> #include <mist/socket.h>
#include <mist/defines.h>
/*LTS-START*/
#ifdef GEOIP
#define GEOIPV4 "GeoIP.dat"
#define GEOIPV6 "GeoIPv6.dat"
#endif
/*LTS-END*/
int spawnForked(Socket::Connection & S){ int spawnForked(Socket::Connection & S){
mistOut tmp(S); mistOut tmp(S);
@ -10,6 +18,21 @@ int spawnForked(Socket::Connection & S){
int main(int argc, char * argv[]) { int main(int argc, char * argv[]) {
Util::Config conf(argv[0], PACKAGE_VERSION); Util::Config conf(argv[0], PACKAGE_VERSION);
mistOut::init(&conf); 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.parseArgs(argc, argv)) {
if (conf.getBool("json")) { if (conf.getBool("json")) {
std::cout << mistOut::capa.toString() << std::endl; std::cout << mistOut::capa.toString() << std::endl;
@ -23,5 +46,11 @@ int main(int argc, char * argv[]) {
return tmp.run(); return tmp.run();
} }
} }
/*LTS-START*/
#ifdef GEOIP
GeoIP_delete(mistOut::geoIP4);
GeoIP_delete(mistOut::geoIP6);
#endif
/*LTS-END*/
return 0; return 0;
} }

View file

@ -12,6 +12,12 @@
#include <mist/timing.h> #include <mist/timing.h>
#include "output.h" #include "output.h"
/*LTS-START*/
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
/*LTS-END*/
namespace Mist { namespace Mist {
JSON::Value Output::capa = JSON::Value(); 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"]["help"] = "The debug level at which messages need to be printed.";
capa["optional"]["debug"]["option"] = "--debug"; capa["optional"]["debug"]["option"] = "--debug";
capa["optional"]["debug"]["type"] = "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) { Output::Output(Socket::Connection & conn) : myConn(conn) {
@ -378,6 +389,273 @@ namespace Mist {
} }
} }
/*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(){ void Output::requestHandler(){
static bool firstData = true;//only the first time, we call onRequest if there's data buffered already. static bool firstData = true;//only the first time, we call onRequest if there's data buffered already.
if ((firstData && myConn.Received().size()) || myConn.spool()){ if ((firstData && myConn.Received().size()) || myConn.spool()){
@ -586,6 +864,12 @@ namespace Mist {
if (statsPage.getData()){ if (statsPage.getData()){
unsigned long long int now = Util::epoch(); unsigned long long int now = Util::epoch();
if (now != lastStats){ if (now != lastStats){
/*LTS-START*/
if (statsPage.getData()[-1] > 127){
myConn.close();
return;
}
/*LTS-END*/
lastStats = now; lastStats = now;
IPC::statExchange tmpEx(statsPage.getData()); IPC::statExchange tmpEx(statsPage.getData());
tmpEx.now(now); tmpEx.now(now);

View file

@ -9,15 +9,20 @@
#include <mist/dtsc.h> #include <mist/dtsc.h>
#include <mist/socket.h> #include <mist/socket.h>
#include <mist/shared_memory.h> #include <mist/shared_memory.h>
/*LTS-START*/
#ifdef GEOIP
#include <GeoIP.h>
#endif
/*LTS-END*/
#include "../io.h" #include "../io.h"
namespace Mist { namespace Mist {
/// This struct keeps packet information sorted in playback order, so the /// This struct keeps packet information sorted in playback order, so the
/// Mist::Output class knows when to buffer which packet. /// Mist::Output class knows when to buffer which packet.
struct sortedPageInfo{ struct sortedPageInfo {
bool operator < (const sortedPageInfo & rhs) const { bool operator < (const sortedPageInfo & rhs) const {
if (time < rhs.time){ if (time < rhs.time) {
return true; return true;
} }
return (time == rhs.time && tid < rhs.tid); return (time == rhs.time && tid < rhs.tid);
@ -41,6 +46,12 @@ namespace Mist {
//static members for initialization and capabilities //static members for initialization and capabilities
static void init(Util::Config * cfg); static void init(Util::Config * cfg);
static JSON::Value capa; static JSON::Value capa;
/*LTS-START*/
#ifdef GEOIP
static GeoIP * geoIP4;
static GeoIP * geoIP6;
#endif
/*LTS-END*/
//non-virtual generic functions //non-virtual generic functions
int run(); int run();
void stats(); void stats();
@ -64,6 +75,14 @@ namespace Mist {
virtual void onFail(); virtual void onFail();
virtual void requestHandler(); virtual void requestHandler();
private://these *should* not be messed with in child classes. 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<unsigned long, unsigned int> currKeyOpen; std::map<unsigned long, unsigned int> currKeyOpen;
void loadPageForKey(long unsigned int trackId, long long int keyNum); void loadPageForKey(long unsigned int trackId, long long int keyNum);
int pageNumForKey(long unsigned int trackId, long long int keyNum); int pageNumForKey(long unsigned int trackId, long long int keyNum);
@ -97,3 +116,4 @@ namespace Mist {
}; };
} }

View file

@ -0,0 +1,669 @@
#include "output_dash_mp4.h"
#include <mist/defines.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
#include <mist/mp4_dash.h>
#include <mist/checksum.h>
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<DTSC::Key>::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<MP4::HVCCArrayEntry> content = hvccBox.getArrays();
for (std::deque<MP4::HVCCArrayEntry>::iterator it = content.begin(); it != content.end(); it++){
for (std::deque<std::string>::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<MP4::HVCCArrayEntry> 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<MP4::HVCCArrayEntry>::iterator it = content.begin(); it != content.end(); it++){
for (std::deque<std::string>::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<MP4::HVCCArrayEntry> content = hvccBox.getArrays();
for (int j = 0; j < myMeta.tracks[tid].keys[keyNum].getParts(); j++){
for (std::deque<MP4::HVCCArrayEntry>::iterator it = content.begin(); it != content.end(); it++){
for (std::deque<std::string>::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<unsigned int, DTSC::Track>::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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
r << "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" type=\"static\" mediaPresentationDuration=\"" << makeTime(lastTime) << "\" minBufferTime=\"PT1.5S\" >" << std::endl;
r << " <ProgramInformation><Title>" << streamName << "</Title></ProgramInformation>" << std::endl;
r << " <Period start=\"PT0S\">" << std::endl;
if (vidInitTrack){
r << " <AdaptationSet id=\"0\" mimeType=\"video/mp4\" width=\"" << myMeta.tracks[vidInitTrack].width << "\" height=\"" << myMeta.tracks[vidInitTrack].height << "\" frameRate=\"" << myMeta.tracks[vidInitTrack].fpks / 1000 << "\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\">" << std::endl;
r << " <SegmentTemplate timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl;
r << " <SegmentTimeline>" << std::endl;
for (int i = 0; i < myMeta.tracks[vidInitTrack].keys.size() - 1; i++){
r << " <S " << (i == 0 ? "t=\"0\" " : "") << "d=\"" << myMeta.tracks[vidInitTrack].keys[i].getLength() << "\" />" << std::endl;
}
int lastDur = myMeta.tracks[vidInitTrack].lastms - myMeta.tracks[vidInitTrack].keys.rbegin()->getTime();
r << " <S d=\"" << lastDur << "\" />" << std::endl;
r << " </SegmentTimeline>" << std::endl;
r << " </SegmentTemplate>" << std::endl;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
if (it->second.codec == "H264"){
MP4::AVCC avccBox;
avccBox.setPayload(it->second.init);
r << " <Representation ";
r << "id=\"" << it->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 << " <Representation ";
r << "id=\"" << it->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 << " </AdaptationSet>" << std::endl;
}
if (audInitTrack){
r << " <AdaptationSet id=\"1\" mimeType=\"audio/mp4\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\" >" << std::endl;
r << " <Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"main\"/>" << std::endl;
r << " <SegmentTemplate timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl;
r << " <SegmentTimeline>" << std::endl;
for (int i = 0; i < myMeta.tracks[audInitTrack].keys.size() - 1; i++){
r << " <S " << (i == 0 ? "t=\"0\" " : "") << "d=\"" << myMeta.tracks[audInitTrack].keys[i].getLength() << "\" />" << std::endl;
}
int lastDur = myMeta.tracks[audInitTrack].lastms - myMeta.tracks[audInitTrack].keys.rbegin()->getTime();
r << " <S d=\"" << lastDur << "\" />" << std::endl;
r << " </SegmentTimeline>" << std::endl;
r << " </SegmentTemplate>" << std::endl;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
if (it->second.codec == "AAC"){
r << " <Representation ";
r << "id=\"" << it->first << "\" ";
r << "codecs=\"mp4a.40.2\" ";
r << "audioSamplingRate=\"" << it->second.rate << "\" ";
r << "bandwidth=\"" << it->second.bps << "\">" << std::endl;
r << " <AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"" << it->second.channels << "\" />" << std::endl;
r << " </Representation>" << std::endl;
}
}
r << " </AdaptationSet>" << std::endl;
}
r << " </Period>" << std::endl;
r << "</MPD>" << 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<unsigned int,DTSC::Track>::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(){}
}

View file

@ -0,0 +1,33 @@
#include "output_http.h"
#include <mist/mp4_generic.h>
#include <mist/http_parser.h>
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<unsigned int, std::map<unsigned int, long long unsigned int> > 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<int,std::string> moovBoxes;
};
}
typedef Mist::OutDashMP4 mistOut;

View file

@ -57,6 +57,19 @@ namespace Mist {
int j = 0; int j = 0;
if (myMeta.tracks[tid].fragments.size()){ if (myMeta.tracks[tid].fragments.size()){
std::deque<DTSC::Fragment>::iterator fragIt = myMeta.tracks[tid].fragments.begin(); std::deque<DTSC::Fragment>::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(); unsigned int firstTime = myMeta.tracks[tid].getKey(fragIt->getNumber()).getTime();
while (fragIt != myMeta.tracks[tid].fragments.end()){ while (fragIt != myMeta.tracks[tid].fragments.end()){
if (myMeta.vod || fragIt->getDuration() > 0){ if (myMeta.vod || fragIt->getDuration() > 0){
@ -160,6 +173,7 @@ namespace Mist {
capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "flash/11"; capa["methods"][0u]["type"] = "flash/11";
capa["methods"][0u]["priority"] = 7ll; capa["methods"][0u]["priority"] = 7ll;
cfg->getOption("startpos", true)[0u] = 0ll;
} }
void OutHDS::sendNext(){ void OutHDS::sendNext(){

View file

@ -11,7 +11,7 @@ namespace Mist {
int audioId = -1; int audioId = -1;
std::string audioName; std::string audioName;
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ for (std::map<unsigned int,DTSC::Track>::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; audioId = it->first;
audioName = it->second.getIdentifier(); audioName = it->second.getIdentifier();
break; break;
@ -19,7 +19,7 @@ namespace Mist {
} }
unsigned int vidTracks = 0; unsigned int vidTracks = 0;
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ for (std::map<unsigned int,DTSC::Track>::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++; vidTracks++;
int bWidth = it->second.bps * 2; int bWidth = it->second.bps * 2;
if (bWidth < 5){ if (bWidth < 5){
@ -84,6 +84,20 @@ namespace Mist {
} }
//only print the last segment when VoD //only print the last segment when VoD
lines.pop_back(); 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"; 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["desc"] = "Enables HTTP protocol Apple-specific streaming (also known as HLS).";
capa["url_rel"] = "/hls/$/index.m3u8"; capa["url_rel"] = "/hls/$/index.m3u8";
capa["url_prefix"] = "/hls/$/"; capa["url_prefix"] = "/hls/$/";
capa["codecs"][0u][0u].append("HEVC");
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("AC3");
capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl"; capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl";
capa["methods"][0u]["priority"] = 9ll; 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){ int OutHLS::canSeekms(unsigned int ms){

View file

@ -3,6 +3,7 @@
#include <mist/mp4.h> #include <mist/mp4.h>
#include <mist/mp4_ms.h> #include <mist/mp4_ms.h>
#include <mist/mp4_generic.h> #include <mist/mp4_generic.h>
#include <mist/mp4_encryption.h> /*LTS*/
#include <mist/base64.h> #include <mist/base64.h>
#include <mist/http_parser.h> #include <mist/http_parser.h>
#include <mist/stream.h> #include <mist/stream.h>
@ -261,7 +262,7 @@ namespace Mist {
int fragCount = 0; int fragCount = 0;
for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++) { for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++) {
if (myMeta.tracks[tid].keys[i].getTime() > seekTime) { 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.setTime(fragCount, myMeta.tracks[tid].keys[i].getTime() * 10000);
fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000); fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000);
fragref_box.setFragmentCount(++fragCount); fragref_box.setFragmentCount(++fragCount);
@ -273,6 +274,37 @@ namespace Mist {
MP4::MOOF moof_box; MP4::MOOF moof_box;
moof_box.setContent(mfhd_box, 0); moof_box.setContent(mfhd_box, 0);
moof_box.setContent(traf_box, 1); 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<long long int> 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. //Setting the correct offsets.
moof_box.setContent(traf_box, 1); moof_box.setContent(traf_box, 1);
trun_box.setDataOffset(moof_box.boxedSize() + 8); trun_box.setDataOffset(moof_box.boxedSize() + 8);
@ -290,10 +322,36 @@ namespace Mist {
H.Clean(); H.Clean();
} }
/*LTS-START*/
std::string OutHSS::protectionHeader(JSON::Value & encParams) {
std::string xmlGen = "<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO><KID>";
xmlGen += encParams["keyid"].asString();
xmlGen += "</KID><LA_URL>";
xmlGen += encParams["la_url"].asString();
xmlGen += "</LA_URL></DATA></WRMHEADER>";
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. ///\brief Builds an index file for HTTP Smooth streaming.
///\param encParams The encryption parameters. /*LTS*/
///\return The index file for HTTP Smooth Streaming. ///\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(); updateMeta();
std::stringstream Result; std::stringstream Result;
Result << "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n"; Result << "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n";
@ -307,6 +365,7 @@ namespace Mist {
long long int maxHeight = 0; long long int maxHeight = 0;
long long int minWidth = 99999999; long long int minWidth = 99999999;
long long int minHeight = 99999999; long long int minHeight = 99999999;
bool encrypted = false;/*LTS*/
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
if (it->second.codec == "AAC") { if (it->second.codec == "AAC") {
audioIters.push_back(it); audioIters.push_back(it);
@ -350,6 +409,7 @@ namespace Mist {
"Url=\"Q({bitrate},{CustomAttributes})/A({start time})\">\n"; "Url=\"Q({bitrate},{CustomAttributes})/A({start time})\">\n";
int index = 0; int index = 0;
for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = audioIters.begin(); it != audioIters.end(); it++) { for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = audioIters.begin(); it != audioIters.end(); it++) {
encrypted |= ((*it)->second.keys.size() == (*it)->second.ivecs.size()); /*LTS*/
Result << "<QualityLevel " Result << "<QualityLevel "
"Index=\"" << index << "\" " "Index=\"" << index << "\" "
"Bitrate=\"" << (*it)->second.bps * 8 << "\" " "Bitrate=\"" << (*it)->second.bps * 8 << "\" "
@ -395,6 +455,7 @@ namespace Mist {
"DisplayHeight=\"" << maxHeight << "\">\n"; "DisplayHeight=\"" << maxHeight << "\">\n";
int index = 0; int index = 0;
for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = videoIters.begin(); it != videoIters.end(); it++) { for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = videoIters.begin(); it != videoIters.end(); it++) {
encrypted |= ((*it)->second.keys.size() == (*it)->second.ivecs.size()); /*LTS*/
//Add video qualities //Add video qualities
Result << "<QualityLevel " Result << "<QualityLevel "
"Index=\"" << index << "\" " "Index=\"" << index << "\" "
@ -427,6 +488,13 @@ namespace Mist {
} }
Result << "</StreamIndex>\n"; Result << "</StreamIndex>\n";
} }
/*LTS-START*/
if (encrypted) {
Result << "<Protection><ProtectionHeader SystemID=\"9a04f079-9840-4286-ab92-e65be0885f95\">";
Result << protectionHeader(encParams);
Result << "</ProtectionHeader></Protection>";
}
/*LTS-END*/
Result << "</SmoothStreamingMedia>\n"; Result << "</SmoothStreamingMedia>\n";
#if DEBUG >= 8 #if DEBUG >= 8
@ -443,7 +511,10 @@ namespace Mist {
H.Clean(); H.Clean();
H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Content-Type", "text/xml");
H.SetHeader("Cache-Control", "no-cache"); H.SetHeader("Cache-Control", "no-cache");
/*LTS
std::string manifest = smoothIndex(); std::string manifest = smoothIndex();
LTS*/
std::string manifest = smoothIndex(encryption);/*LTS*/
H.SetBody(manifest); H.SetBody(manifest);
H.SendResponse("200", "OK", myConn); H.SendResponse("200", "OK", myConn);
H.Clean(); H.Clean();
@ -454,8 +525,16 @@ namespace Mist {
} }
} }
/*LTS-START*/
void OutHSS::initialize() { void OutHSS::initialize() {
Output::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*/
} }

View file

@ -13,7 +13,11 @@ namespace Mist {
void sendHeader(); void sendHeader();
protected: protected:
JSON::Value encryption; JSON::Value encryption;
std::string protectionHeader(JSON::Value & encParams);/*LTS*/
/*LTS
std::string smoothIndex(); std::string smoothIndex();
LTS*/
std::string smoothIndex(JSON::Value encParams = JSON::Value());/*LTS*/
int canSeekms(unsigned int ms); int canSeekms(unsigned int ms);
int keysToSend; int keysToSend;
int myTrackStor; int myTrackStor;

View file

@ -164,6 +164,15 @@ namespace Mist {
// send logo icon // send logo icon
if (H.url.length() > 4 && H.url.substr(H.url.length() - 4, 4) == ".ico"){ 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(); H.Clean();
#include "../icon.h" #include "../icon.h"
H.SetHeader("Content-Type", "image/x-icon"); H.SetHeader("Content-Type", "image/x-icon");
@ -304,6 +313,7 @@ namespace Mist {
it->second.removeMember("fragments"); it->second.removeMember("fragments");
it->second.removeMember("keys"); it->second.removeMember("keys");
it->second.removeMember("parts"); it->second.removeMember("parts");
it->second.removeMember("ivecs");/*LTS*/
} }
//create a set for storing source information //create a set for storing source information

View file

@ -17,8 +17,10 @@ namespace Mist {
capa["url_match"] = "/$.ts"; capa["url_match"] = "/$.ts";
capa["socket"] = "http_ts"; capa["socket"] = "http_ts";
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][0u].append("HEVC");
capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("AC3");
capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "html5/video/mp2t"; capa["methods"][0u]["type"] = "html5/video/mp2t";
capa["methods"][0u]["priority"] = 1ll; capa["methods"][0u]["priority"] = 1ll;

View file

@ -15,8 +15,10 @@ namespace Mist {
capa["url_rel"] = "/$.mp4"; capa["url_rel"] = "/$.mp4";
capa["url_match"] = "/$.mp4"; capa["url_match"] = "/$.mp4";
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][0u].append("HEVC");
capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("AC3");
capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "html5/video/mp4"; capa["methods"][0u]["type"] = "html5/video/mp4";
capa["methods"][0u]["priority"] = 8ll; capa["methods"][0u]["priority"] = 8ll;
@ -115,6 +117,13 @@ namespace Mist {
avccBox.setPayload(thisTrack.init); avccBox.setPayload(thisTrack.init);
vse.setCLAP(avccBox); 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); stsdBox.setEntry(vse,0);
}else if(thisTrack.type == "audio"){//boxname = codec }else if(thisTrack.type == "audio"){//boxname = codec
MP4::AudioSampleEntry ase; MP4::AudioSampleEntry ase;
@ -124,12 +133,44 @@ namespace Mist {
}else if (thisTrack.codec == "MP3"){ }else if (thisTrack.codec == "MP3"){
ase.setCodec("mp4a"); ase.setCodec("mp4a");
ase.setDataReferenceIndex(1); ase.setDataReferenceIndex(1);
}else if (thisTrack.codec == "AC3"){
ase.setCodec("ac-3");
ase.setDataReferenceIndex(1);
} }
ase.setSampleRate(thisTrack.rate); ase.setSampleRate(thisTrack.rate);
ase.setChannelCount(thisTrack.channels); ase.setChannelCount(thisTrack.channels);
ase.setSampleSize(thisTrack.size); 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); MP4::ESDS esdsBox(thisTrack.init);
ase.setCodecBox(esdsBox); ase.setCodecBox(esdsBox);
}
stsdBox.setEntry(ase,0); stsdBox.setEntry(ase,0);
} }
stblBox.setContent(stsdBox,offset++); stblBox.setContent(stsdBox,offset++);
@ -138,6 +179,7 @@ namespace Mist {
MP4::STTS sttsBox; MP4::STTS sttsBox;
sttsBox.setVersion(0); sttsBox.setVersion(0);
if (thisTrack.parts.size()){ 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){ for (unsigned int part = thisTrack.parts.size(); part > 0; --part){
MP4::STTSEntry newEntry; MP4::STTSEntry newEntry;
newEntry.sampleCount = 1; newEntry.sampleCount = 1;
@ -441,6 +483,14 @@ namespace Mist {
} }
void OutProgressiveMP4::onHTTP(){ 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(); initialize();
parseData = true; parseData = true;
wantRequest = false; wantRequest = false;

View file

@ -12,6 +12,9 @@ namespace Mist {
if (trackID < rhs.trackID){ if (trackID < rhs.trackID){
return true; return true;
} }
if (trackID == rhs.trackID){
return endTime < rhs.endTime;
}
} }
return false; return false;
} }

View file

@ -461,11 +461,12 @@ namespace Mist {
Util::sanitizeName(streamName); Util::sanitizeName(streamName);
//pull the server configuration //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::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE); ///< Contains server configuration and capabilities
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
configLock.wait(); 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){
if (streamCfg.getMember("source").asString().substr(0, 7) != "push://"){ 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()); 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{ }else{
std::string source = streamCfg.getMember("source").asString().substr(7); std::string source = streamCfg.getMember("source").asString().substr(7);
std::string IP = source.substr(0, source.find('@')); 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 (IP != ""){
if (!myConn.isAddress(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()); DEBUG_MSG(DLVL_FAIL, "Push from %s to %s rejected - source host not whitelisted", myConn.getHost().c_str(), streamName.c_str());

406
src/output/output_rtsp.cpp Normal file
View file

@ -0,0 +1,406 @@
#include <mist/defines.h>
#include <mist/auth.h>
#include <mist/base64.h>
#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<unsigned int, DTSC::Track>::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<unsigned int, DTSC::Track>::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<unsigned int, DTSC::Track>::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<int, trackmeta>::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<int, long long int> timeMap; //Keeps track of temporary timestamp data for the upcoming seek.
for (std::map<int, trackmeta>::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;
}
}

47
src/output/output_rtsp.h Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include "output.h"
#include <mist/socket.h>
#include <mist/rtp.h>
#include <mist/http_parser.h>
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<int, trackmeta> 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;

View file

@ -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"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces";
capa["optional"]["tracks"]["type"] = "str"; capa["optional"]["tracks"]["type"] = "str";
capa["optional"]["tracks"]["option"] = "--tracks"; capa["optional"]["tracks"]["option"] = "--tracks";
capa["codecs"][0u][0u].append("HEVC");
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("AC3");
cfg->addOption("streamname", cfg->addOption("streamname",
JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}"));
cfg->addOption("tracks", cfg->addOption("tracks",

View file

@ -4,6 +4,7 @@ namespace Mist {
TSOutput::TSOutput(Socket::Connection & conn) : TS_BASECLASS(conn){ TSOutput::TSOutput(Socket::Connection & conn) : TS_BASECLASS(conn){
packCounter=0; packCounter=0;
haveAvcc = false; haveAvcc = false;
haveHvcc = false;
until=0xFFFFFFFFFFFFFFFFull; until=0xFFFFFFFFFFFFFFFFull;
setBlocking(true); setBlocking(true);
sendRepeatingHeaders = false; sendRepeatingHeaders = false;
@ -81,6 +82,16 @@ namespace Mist {
bs = avccbox.asAnnexB(); bs = avccbox.asAnnexB();
extraSize += bs.size(); 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; unsigned int watKunnenWeIn1Ding = 65490-13;
@ -106,6 +117,13 @@ namespace Mist {
fillPacket(bs.data(), bs.size()); fillPacket(bs.data(), bs.size());
alreadySent += 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){ while (i + 4 < (unsigned int)dataLen){

View file

@ -25,6 +25,10 @@ namespace Mist {
bool haveAvcc; bool haveAvcc;
MP4::AVCC avccbox; MP4::AVCC avccbox;
bool appleCompat; bool appleCompat;
/*LTS-START*/
bool haveHvcc;
MP4::HVCC hvccbox;
/*LTS-END*/
bool sendRepeatingHeaders; bool sendRepeatingHeaders;
long long unsigned int until; long long unsigned int until;
long long unsigned int lastVid; long long unsigned int lastVid;

View file

@ -0,0 +1,99 @@
#include "output_ts_push.h"
#include <mist/http_parser.h>
#include <mist/defines.h>
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);
}
}

View file

@ -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;