LTS Commits
This commit is contained in:
parent
f24d97b510
commit
4bdbd82f66
72 changed files with 8245 additions and 105 deletions
|
@ -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
124
Makefile
|
@ -7,13 +7,30 @@ libdir = $(exec_prefix)/lib
|
||||||
PACKAGE_VERSION := $(shell git describe --tags 2> /dev/null || cat VERSION 2> /dev/null || echo "Unknown")
|
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,12 +74,29 @@ 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 $@
|
||||||
|
|
||||||
analysers: MistAnalyserRTMP
|
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
|
||||||
|
@ -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,11 +170,32 @@ MistInBuffer: override CPPFLAGS += "-DINPUTTYPE=\"input_buffer.h\""
|
||||||
MistInBuffer: src/input/mist_in.cpp src/input/input.cpp src/input/input_buffer.cpp src/io.cpp
|
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)
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
|
55
lib/dtsc.cpp
55
lib/dtsc.cpp
|
@ -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;
|
||||||
|
|
26
lib/dtsc.h
26
lib/dtsc.h
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
62
lib/encryption.cpp
Normal 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
8
lib/encryption.h
Normal 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);
|
||||||
|
}
|
61
lib/mp4.cpp
61
lib/mp4.cpp
|
@ -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
240
lib/mp4_dash.cpp
Normal 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
73
lib/mp4_dash.h
Normal 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
456
lib/mp4_encryption.cpp
Normal 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
104
lib/mp4_encryption.h
Normal 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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,47 +1318,52 @@ 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;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
239
lib/rtp.cpp
Normal 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
62
lib/rtp.h
Normal 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();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -79,6 +79,12 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
|
||||||
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 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("+ "));
|
||||||
|
@ -89,6 +95,13 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
|
||||||
configLock.post();//unlock the config semaphore
|
configLock.post();//unlock the config semaphore
|
||||||
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.
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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() );
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
209
src/analysers/rtp_analyser.cpp
Normal file
209
src/analysers/rtp_analyser.cpp
Normal 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();
|
||||||
|
}
|
197
src/analysers/rtsp_rtp_analyser.cpp
Normal file
197
src/analysers/rtsp_rtp_analyser.cpp
Normal 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"));
|
||||||
|
}
|
84
src/analysers/stats_analyser.cpp
Normal file
84
src/analysers/stats_analyser.cpp
Normal 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
189
src/analysers/ts_analyser.cpp
Executable 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"));
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = "";
|
||||||
|
|
403
src/controller/controller_limits.cpp
Normal file
403
src/controller/controller_limits.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
21
src/controller/controller_limits.h
Normal file
21
src/controller/controller_limits.h
Normal 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);
|
||||||
|
}
|
|
@ -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(){
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
253
src/controller/controller_updater.cpp
Normal file
253
src/controller/controller_updater.cpp
Normal 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
|
21
src/controller/controller_updater.h
Normal file
21
src/controller/controller_updater.h
Normal 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
|
138
src/controller/controller_uplink.cpp
Normal file
138
src/controller/controller_uplink.cpp
Normal 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
|
||||||
|
}
|
||||||
|
}
|
3
src/controller/controller_uplink.h
Normal file
3
src/controller/controller_uplink.h
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
namespace Controller {
|
||||||
|
void uplinkConnection(void * np);
|
||||||
|
}
|
281
src/input/input_av.cpp
Normal file
281
src/input/input_av.cpp
Normal 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
32
src/input/input_av.h
Normal 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;
|
|
@ -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;
|
||||||
|
|
|
@ -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*/
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
src/input/input_folder.cpp
Normal file
19
src/input/input_folder.cpp
Normal 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
14
src/input/input_folder.h
Normal 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
407
src/input/input_ismv.cpp
Normal 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
51
src/input/input_ismv.h
Normal 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;
|
||||||
|
|
|
@ -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
632
src/input/input_mp4.cpp
Normal 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
102
src/input/input_mp4.h
Normal 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
516
src/input/input_ts.cpp
Executable 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
81
src/input/input_ts.h
Executable 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;
|
||||||
|
|
53
src/input/mist_in_folder.cpp
Normal file
53
src/input/mist_in_folder.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -377,6 +388,273 @@ namespace Mist {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*LTS-START*/
|
||||||
|
bool Output::onList(std::string ip, std::string list){
|
||||||
|
if (list == ""){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string entry;
|
||||||
|
std::string lowerIpv6;//lower-case
|
||||||
|
std::string upperIpv6;//full-caps
|
||||||
|
do{
|
||||||
|
entry = list.substr(0,list.find(" "));//make sure we have a single entry
|
||||||
|
lowerIpv6 = "::ffff:" + entry;
|
||||||
|
upperIpv6 = "::FFFF:" + entry;
|
||||||
|
if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
long long unsigned int starPos = entry.find("*");
|
||||||
|
if (starPos == std::string::npos){
|
||||||
|
if (ip == entry){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if (starPos == 0){//beginning of the filter
|
||||||
|
if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if (starPos == entry.size() - 1){//end of the filter
|
||||||
|
if (ip.find(entry.substr(0, entry.size() - 1)) == 0 ){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0 ){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0 ){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
Log("CONF","Invalid list entry detected: " + entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.erase(0, entry.size() + 1);
|
||||||
|
}while (list != "");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Output::Log(std::string type, std::string message){
|
||||||
|
/// \todo These logs need to show up in the controller.
|
||||||
|
/// \todo Additionally, the triggering and untriggering of limits should be recorded in the controller as well.
|
||||||
|
if (type == "HLIM"){
|
||||||
|
DEBUG_MSG(DLVL_HIGH, "HardLimit Triggered: %s", message.c_str());
|
||||||
|
}
|
||||||
|
if (type == "SLIM"){
|
||||||
|
DEBUG_MSG(DLVL_HIGH, "SoftLimit Triggered: %s", message.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Output::hostLookup(std::string ip){
|
||||||
|
struct sockaddr_in6 sa;
|
||||||
|
char hostName[1024];
|
||||||
|
char service[20];
|
||||||
|
if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){
|
||||||
|
return "\n";
|
||||||
|
}
|
||||||
|
sa.sin6_family = AF_INET6;
|
||||||
|
sa.sin6_port = 0;
|
||||||
|
sa.sin6_flowinfo = 0;
|
||||||
|
sa.sin6_scope_id = 0;
|
||||||
|
int tmpRet = getnameinfo((struct sockaddr*)&sa, sizeof sa, hostName, sizeof hostName, service, sizeof service, NI_NAMEREQD );
|
||||||
|
if ( tmpRet == 0){
|
||||||
|
return hostName;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Output::isBlacklisted(std::string host, std::string streamName, int timeConnected){
|
||||||
|
return false;//blacklisting temporarily disabled for performance reasons
|
||||||
|
JSON::Value Storage = JSON::fromFile(Util::getTmpFolder() + "streamlist");
|
||||||
|
std::string myHostName = hostLookup(host);
|
||||||
|
if (myHostName == "\n"){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string myCountryName = getCountry(host);
|
||||||
|
JSON::ArrIter limitIt;
|
||||||
|
bool hasWhitelist = false;
|
||||||
|
bool hostOnWhitelist = false;
|
||||||
|
if (Storage["streams"].isMember(streamName)){
|
||||||
|
if (Storage["streams"][streamName].isMember("limits") && Storage["streams"][streamName]["limits"].size()){
|
||||||
|
for (limitIt = Storage["streams"][streamName]["limits"].ArrBegin(); limitIt != Storage["streams"][streamName]["limits"].ArrEnd(); limitIt++){
|
||||||
|
if ((*limitIt)["name"].asString() == "host"){
|
||||||
|
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||||
|
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||||
|
if (myHostName == ""){
|
||||||
|
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if ( !onList(myHostName, (*limitIt)["value"].asStringRef().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asStringRef() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if ((*limitIt)["value"].asStringRef().size() > 1 && (*limitIt)["value"].asStringRef()[0] == '-'){
|
||||||
|
if (onList(host, (*limitIt)["value"].asStringRef().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asStringRef() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asStringRef() == "hard"){
|
||||||
|
Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((*limitIt)["name"].asString() == "geo"){
|
||||||
|
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||||
|
if (myCountryName == ""){
|
||||||
|
if ((*limitIt)["type"].asString() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asString() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if ((*limitIt)["value"].asString()[0] == '-'){
|
||||||
|
if (onList(myCountryName, (*limitIt)["value"].asString().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asString() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Storage["config"]["limits"].size()){
|
||||||
|
for (limitIt = Storage["config"]["limits"].ArrBegin(); limitIt != Storage["config"]["limits"].ArrEnd(); limitIt++){
|
||||||
|
if ((*limitIt)["name"].asString() == "host"){
|
||||||
|
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||||
|
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||||
|
if (myHostName == ""){
|
||||||
|
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asString() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if ((*limitIt)["value"].asString()[0] == '-'){
|
||||||
|
if (onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asString() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asString() == "hard"){
|
||||||
|
Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((*limitIt)["name"].asString() == "geo"){
|
||||||
|
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||||
|
if (myCountryName == ""){
|
||||||
|
if ((*limitIt)["type"].asString() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asString() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if ((*limitIt)["value"].asStringRef().size() > 1 && (*limitIt)["value"].asStringRef()[0] == '-'){
|
||||||
|
if (onList(myCountryName, (*limitIt)["value"].asStringRef().substr(1))){
|
||||||
|
if ((*limitIt)["type"].asString() == "hard"){
|
||||||
|
Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasWhitelist){
|
||||||
|
if (hostOnWhitelist || myHostName == ""){
|
||||||
|
return false;
|
||||||
|
}else{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef GEOIP
|
||||||
|
GeoIP * Output::geoIP4 = 0;
|
||||||
|
GeoIP * Output::geoIP6 = 0;
|
||||||
|
#endif
|
||||||
|
std::string Output::getCountry(std::string ip){
|
||||||
|
char * code = NULL;
|
||||||
|
#ifdef GEOIP
|
||||||
|
if (geoIP4){
|
||||||
|
code = (char*)GeoIP_country_code_by_addr(geoIP4, ip.c_str());
|
||||||
|
}
|
||||||
|
if (!code && geoIP6){
|
||||||
|
code = (char*)GeoIP_country_code_by_addr_v6(geoIP6, ip.c_str());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!code){
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
/*LTS-END*/
|
||||||
|
|
||||||
void Output::requestHandler(){
|
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.
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -82,7 +101,7 @@ namespace Mist {
|
||||||
unsigned int maxSkipAhead;///< Maximum ms that we will go ahead of the intended timestamps.
|
unsigned int maxSkipAhead;///< Maximum ms that we will go ahead of the intended timestamps.
|
||||||
unsigned int minSkipAhead;///< Minimum ms that we will go ahead of the intended timestamps.
|
unsigned int minSkipAhead;///< Minimum ms that we will go ahead of the intended timestamps.
|
||||||
unsigned int realTime;///< Playback speed times 1000 (1000 == 1.0X). Zero is infinite.
|
unsigned int realTime;///< Playback speed times 1000 (1000 == 1.0X). Zero is infinite.
|
||||||
|
|
||||||
//Read/write status variables
|
//Read/write status variables
|
||||||
Socket::Connection & myConn;///< Connection to the client.
|
Socket::Connection & myConn;///< Connection to the client.
|
||||||
|
|
||||||
|
@ -97,3 +116,4 @@ namespace Mist {
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
669
src/output/output_dash_mp4.cpp
Normal file
669
src/output/output_dash_mp4.cpp
Normal 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(){}
|
||||||
|
}
|
33
src/output/output_dash_mp4.h
Normal file
33
src/output/output_dash_mp4.h
Normal 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;
|
|
@ -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(){
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
406
src/output/output_rtsp.cpp
Normal 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
47
src/output/output_rtsp.h
Normal 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;
|
|
@ -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",
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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;
|
||||||
|
|
99
src/output/output_ts_push.cpp
Normal file
99
src/output/output_ts_push.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
18
src/output/output_ts_push.h
Normal file
18
src/output/output_ts_push.h
Normal 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;
|
Loading…
Add table
Reference in a new issue