├── layerinfo.go ├── wrapper ├── README.md ├── module.go ├── mediaserver_wrap.h └── mediaserver.i ├── packetizer ├── packetizer.go ├── opus.go ├── vp8.go └── h264.go ├── docker_build.sh ├── go.mod ├── .gitignore ├── .gitmodules ├── go.sum ├── module.go ├── config.mk ├── .travis.yml ├── Makefile ├── recordertrack.go ├── refresher.go ├── util.go ├── recorder.go ├── mediaframesession.go ├── README.md ├── mediaframemultiplexer.go ├── incomingstreamtrackmirrored.go ├── manual.md ├── streamersession.go ├── endpoint.go ├── outgoingstreamtrack.go ├── outgoingstream.go ├── transport_test.go ├── incomingstream.go ├── transponder.go ├── incomingstreamtrack.go ├── LICENSE └── transport.go /layerinfo.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | const MaxLayerId = 0xFF 4 | -------------------------------------------------------------------------------- /wrapper/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | swig -go -c++ -cgo -intgosize 64 mediaserver.i 5 | -------------------------------------------------------------------------------- /packetizer/packetizer.go: -------------------------------------------------------------------------------- 1 | package packetizer 2 | 3 | type Packetizer interface { 4 | Packetize(payload []byte, mtu int) [][]byte 5 | } 6 | -------------------------------------------------------------------------------- /docker_build.sh: -------------------------------------------------------------------------------- 1 | docker run --rm -it \ 2 | --mount type=bind,source=/Users/xiang/Work/media-server-go,target=/media-server-go \ 3 | ubuntu:18.04 \ 4 | bash 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/notedit/media-server-go 2 | 3 | require ( 4 | github.com/gofrs/uuid v3.1.0+incompatible 5 | github.com/notedit/sdp v0.0.4 6 | ) 7 | 8 | go 1.13 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | 15 | .vscode/ 16 | .idea/ 17 | 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /packetizer/opus.go: -------------------------------------------------------------------------------- 1 | package packetizer 2 | 3 | type OpusPacketier struct{} 4 | 5 | func (p *OpusPacketier) Packetize(payload []byte, mtu int) [][]byte { 6 | 7 | if payload == nil { 8 | return [][]byte{} 9 | } 10 | 11 | out := make([]byte, len(payload)) 12 | copy(out, payload) 13 | return [][]byte{out} 14 | } 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "media-server"] 2 | path = media-server 3 | url = https://github.com/notedit/media-server.git 4 | [submodule "thirdparty/libsrtp"] 5 | path = thirdparty/libsrtp 6 | url = https://github.com/cisco/libsrtp.git 7 | [submodule "thirdparty/mp4v2"] 8 | path = thirdparty/mp4v2 9 | url = https://github.com/medooze/mp4v2.git 10 | [submodule "thirdparty/openssl"] 11 | path = thirdparty/openssl 12 | url = https://github.com/openssl/openssl.git 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Jeffail/gabs v1.1.1 h1:V0uzR08Hj22EX8+8QMhyI9sX2hwRu+/RJhJUmnwda/E= 2 | github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= 3 | github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= 4 | github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 5 | github.com/notedit/sdp v0.0.4 h1:P4L8HbZ8SfzrRDE2m3zPnkHhcSdr/0sZkapKo0lyDJs= 6 | github.com/notedit/sdp v0.0.4/go.mod h1:v7SdJxYpW6sY8RhA2KX14mmIHXKC0Kl/XrEQwaQJ7lM= 7 | -------------------------------------------------------------------------------- /module.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | native "github.com/notedit/media-server-go/wrapper" 5 | ) 6 | 7 | func init() { 8 | native.MediaServerInitialize() 9 | } 10 | 11 | func EnableLog(flag bool) { 12 | native.MediaServerEnableLog(flag) 13 | } 14 | 15 | func EnableDebug(flag bool) { 16 | native.MediaServerEnableDebug(flag) 17 | } 18 | 19 | func SetPortRange(minPort, maxPort int) bool { 20 | return native.MediaServerSetPortRange(minPort, maxPort) 21 | } 22 | 23 | func EnableUltraDebug(flag bool) { 24 | native.MediaServerEnableUltraDebug(flag) 25 | } 26 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | ################################# 2 | # Config file 3 | ################################## 4 | 5 | 6 | LOG = yes 7 | DEBUG = no 8 | SANITIZE = no 9 | STATIC = yes 10 | STATIC_OPENSSL = yes 11 | STATIC_LIBSRTP = yes 12 | STATIC_LIBMP4 = yes 13 | OPENSSL_SRC = ${ROOT_DIR}/thirdparty/openssl 14 | LIBSRTP_SRC = ${ROOT_DIR}/thirdparty/libsrtp 15 | LIBMP4_SRC = ${ROOT_DIR}/thirdparty/mp4v2 16 | OPENSSL_DIR = ${ROOT_DIR}/thirdparty/openssl/build 17 | LIBSRTP_DIR = ${ROOT_DIR}/thirdparty/libsrtp/build 18 | LIBMP4_DIR = ${ROOT_DIR}/thirdparty/mp4v2/build 19 | VADWEBRTC = yes 20 | SRCDIR = ${ROOT_DIR}/media-server 21 | IMAGEMAGICK = no 22 | 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | sudo: true 6 | 7 | language: go 8 | 9 | go: 10 | - "1.13" 11 | - "1.14" 12 | 13 | matrix: 14 | include: 15 | - os: linux 16 | dist: xenial 17 | - os: osx 18 | osx_image: xcode10.2 19 | 20 | 21 | before_install: 22 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y; fi 23 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get update -qq; fi 24 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get install -y autoconf libtool automake g++-7; fi 25 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 90; fi 26 | 27 | 28 | install: 29 | - make 30 | 31 | 32 | script: 33 | - go test -v 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /packetizer/vp8.go: -------------------------------------------------------------------------------- 1 | package packetizer 2 | 3 | 4 | 5 | const ( 6 | vp8HeaderSize = 1 7 | ) 8 | 9 | type VP8Packetier struct{} 10 | 11 | func (p *VP8Packetier) Packetize(payload []byte, mtu int) (payloads [][]byte) { 12 | 13 | maxFragmentSize := mtu - vp8HeaderSize 14 | 15 | payloadData := payload 16 | payloadDataRemaining := len(payload) 17 | 18 | payloadDataIndex := 0 19 | 20 | if min(maxFragmentSize, payloadDataRemaining) <= 0 { 21 | return payloads 22 | } 23 | 24 | for payloadDataRemaining > 0 { 25 | currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) 26 | out := make([]byte, vp8HeaderSize+currentFragmentSize) 27 | if payloadDataRemaining == len(payload) { 28 | out[0] = 0x10 29 | } 30 | 31 | copy(out[vp8HeaderSize:], payloadData[payloadDataIndex:payloadDataIndex+currentFragmentSize]) 32 | payloads = append(payloads, out) 33 | 34 | payloadDataRemaining -= currentFragmentSize 35 | payloadDataIndex += currentFragmentSize 36 | } 37 | 38 | return 39 | } -------------------------------------------------------------------------------- /wrapper/module.go: -------------------------------------------------------------------------------- 1 | package native 2 | 3 | 4 | /* 5 | #cgo CFLAGS: -Wno-deprecated 6 | #cgo CXXFLAGS: -std=c++1z 7 | #cgo CPPFLAGS: -I${SRCDIR}/../thirdparty/openssl/build/include/ 8 | #cgo CPPFLAGS: -I${SRCDIR}/../thirdparty/libsrtp/build/include/ 9 | #cgo CPPFLAGS: -I${SRCDIR}/../thirdparty/mp4v2/build/include/ 10 | #cgo CPPFLAGS: -I${SRCDIR}/../media-server/ext/crc32c/include/ 11 | #cgo CPPFLAGS: -I${SRCDIR}/../media-server/ext/libdatachannels/src/ 12 | #cgo CPPFLAGS: -I${SRCDIR}/../media-server/ext/libdatachannels/src/internal/ 13 | #cgo CPPFLAGS: -I${SRCDIR}/../media-server/include/ 14 | #cgo CPPFLAGS: -I${SRCDIR}/../media-server/src/ 15 | #cgo LDFLAGS: ${SRCDIR}/../media-server/bin/release/libmediaserver.a 16 | #cgo LDFLAGS: ${SRCDIR}/../thirdparty/openssl/build/libssl.a 17 | #cgo LDFLAGS: ${SRCDIR}/../thirdparty/openssl/build/libcrypto.a 18 | #cgo LDFLAGS: ${SRCDIR}/../thirdparty/libsrtp/build/libsrtp2.a 19 | #cgo LDFLAGS: ${SRCDIR}/../thirdparty/mp4v2/build/libmp4v2.a 20 | #cgo LDFLAGS: -ldl 21 | */ 22 | import "C" 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export ROOT_DIR=${PWD} 2 | 3 | include config.mk 4 | 5 | 6 | CPPFLAGS = -I${ROOT_DIR}/media-server/ext/crc32/include/ -I${ROOT_DIR}/media-server/ext/libdatachannels/ -I${ROOT_DIR}/media-server/ext/libdatachannels/src/ 7 | 8 | all:OPENSSL SRTP MP4V2 MEDIASERVER_STATIC 9 | echo $(ROOT_DIR) 10 | 11 | OPENSSL: 12 | cd ${OPENSSL_SRC} && export KERNEL_BITS=64 && ./config --prefix=${OPENSSL_DIR} && make && make install && cp -rf ${OPENSSL_DIR}/lib/*.a ${OPENSSL_DIR}/ 13 | 14 | 15 | SRTP: 16 | cd ${LIBSRTP_SRC} && ./configure --prefix=${LIBSRTP_DIR} && make && make install && cp -rf ${LIBSRTP_DIR}/lib/*.a ${LIBSRTP_DIR}/ 17 | 18 | 19 | MP4V2: 20 | cd ${LIBMP4_SRC} && autoreconf -i && ./configure --prefix=${LIBMP4_DIR} && make && make install && cp -rf ${LIBMP4_DIR}/lib/* ${LIBMP4_DIR}/ 21 | 22 | 23 | MEDIASERVER_STATIC: 24 | cp config.mk ./media-server/ && make -C media-server libmediaserver.a CPPFLAGS=${CPPFLAGS} 25 | echo ${ROOT_DIR} 26 | 27 | ECHO: 28 | echo $(ROOT_DIR) 29 | echo $(OPENSSL_DIR) 30 | 31 | -------------------------------------------------------------------------------- /recordertrack.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | type RecorderTrackStopListener func() 4 | 5 | // RecorderTrack a track to record 6 | type RecorderTrack struct { 7 | id string 8 | track *IncomingStreamTrack 9 | encoding *Encoding 10 | } 11 | 12 | // NewRecorderTrack create a new recorder track 13 | func NewRecorderTrack(id string, track *IncomingStreamTrack, encoding *Encoding) *RecorderTrack { 14 | 15 | recorderTrack := &RecorderTrack{} 16 | recorderTrack.id = id 17 | recorderTrack.track = track 18 | recorderTrack.encoding = encoding 19 | 20 | return recorderTrack 21 | } 22 | 23 | // GetID get recorder track id 24 | func (r *RecorderTrack) GetID() string { 25 | return r.id 26 | } 27 | 28 | // GetTrack get internal IncomingStreamTrack 29 | func (r *RecorderTrack) GetTrack() *IncomingStreamTrack { 30 | return r.track 31 | } 32 | 33 | // GetEncoding get encoding info 34 | func (r *RecorderTrack) GetEncoding() *Encoding { 35 | return r.encoding 36 | } 37 | 38 | // Stop stop the recorder track 39 | func (r *RecorderTrack) Stop() { 40 | 41 | if r.track == nil { 42 | return 43 | } 44 | 45 | r.track = nil 46 | r.encoding = nil 47 | } 48 | -------------------------------------------------------------------------------- /refresher.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type Refresher struct { 9 | period int 10 | tracks map[string]*IncomingStreamTrack 11 | ticker *time.Ticker 12 | sync.Mutex 13 | } 14 | 15 | func NewRefresher(period int) *Refresher { 16 | refresher := &Refresher{} 17 | refresher.tracks = map[string]*IncomingStreamTrack{} 18 | refresher.period = period 19 | 20 | return refresher 21 | } 22 | 23 | func (r *Refresher) Add(incom *IncomingStreamTrack) { 24 | 25 | if incom.GetMedia() == "video" { 26 | r.Lock() 27 | r.tracks[incom.GetID()] = incom 28 | r.Unlock() 29 | } 30 | 31 | if r.ticker == nil { 32 | r.ticker = time.NewTicker(time.Duration(r.period) * time.Millisecond) 33 | go func() { 34 | for _ = range r.ticker.C { 35 | for _, track := range r.tracks { 36 | track.Refresh() 37 | } 38 | } 39 | }() 40 | } 41 | } 42 | 43 | func (r *Refresher) AddStream(incoming *IncomingStream) { 44 | 45 | for _, track := range incoming.GetTracks() { 46 | if track.GetMedia() == "video" { 47 | r.Add(track) 48 | } 49 | } 50 | } 51 | 52 | func (r *Refresher) Stop() { 53 | 54 | if r.ticker != nil { 55 | r.ticker.Stop() 56 | r.ticker = nil 57 | } 58 | r.tracks = nil 59 | } 60 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | const ssrcMin uint = 1000000000 8 | const ssrcMax uint = 4294967295 9 | 10 | var ssrcValue = ssrcMin 11 | 12 | func Min(x, y int) int { 13 | if x < y { 14 | return x 15 | } 16 | return y 17 | } 18 | 19 | func Max(x, y int) int { 20 | if x < y { 21 | return y 22 | } 23 | return x 24 | } 25 | 26 | func NextSSRC() uint { 27 | 28 | if ssrcValue == ssrcMax { 29 | ssrcValue = ssrcMin 30 | } 31 | ssrcValue = ssrcValue + 1 32 | return ssrcValue 33 | } 34 | 35 | func u32be(b []byte) (i uint32) { 36 | i = uint32(b[0]) 37 | i <<= 8 38 | i |= uint32(b[1]) 39 | i <<= 8 40 | i |= uint32(b[2]) 41 | i <<= 8 42 | i |= uint32(b[3]) 43 | return 44 | } 45 | 46 | var nalu_prefix = []byte{0, 0, 0, 1} 47 | 48 | func annexbConvert(avc []byte) ([]byte, error) { 49 | if len(avc) < 4 { 50 | return nil, errors.New("too short") 51 | } 52 | val4 := u32be(avc) 53 | _val4 := val4 54 | _b := avc[4:] 55 | annexb := []byte{} 56 | 57 | for { 58 | annexb = append(annexb, nalu_prefix...) 59 | annexb = append(annexb, _b[:_val4]...) 60 | _b = _b[_val4:] 61 | if len(_b) < 4 { 62 | break 63 | } 64 | _val4 = u32be(_b) 65 | _b = _b[4:] 66 | if _val4 > uint32(len(_b)) { 67 | break 68 | } 69 | } 70 | 71 | return annexb, nil 72 | } 73 | -------------------------------------------------------------------------------- /recorder.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | native "github.com/notedit/media-server-go/wrapper" 8 | ) 9 | 10 | // Recorder represent a file recorder 11 | type Recorder struct { 12 | tracks map[string]*RecorderTrack 13 | recorder native.MP4RecorderFacade 14 | ticker *time.Ticker 15 | refresher *Refresher 16 | maxTrackId int 17 | } 18 | 19 | // NewRecorder create a new recorder 20 | func NewRecorder(filename string, waitForIntra bool, refresh int) *Recorder { 21 | recorder := &Recorder{} 22 | recorder.recorder = native.NewMP4RecorderFacade() 23 | recorder.recorder.Create(filename) 24 | recorder.recorder.Record(waitForIntra) 25 | recorder.tracks = map[string]*RecorderTrack{} 26 | recorder.maxTrackId = 1 27 | 28 | if refresh > 0 { 29 | recorder.refresher = NewRefresher(refresh) 30 | } 31 | 32 | return recorder 33 | } 34 | 35 | // Record start record an incoming track 36 | func (r *Recorder) Record(incoming *IncomingStreamTrack) { 37 | 38 | for _, encoding := range incoming.GetEncodings() { 39 | encoding.GetDepacketizer().AddMediaListener(r.recorder) 40 | 41 | r.maxTrackId += 1 42 | recorderTrack := NewRecorderTrack(strconv.Itoa(r.maxTrackId), incoming, encoding) 43 | r.tracks[recorderTrack.GetID()] = recorderTrack 44 | } 45 | 46 | if r.refresher != nil { 47 | r.refresher.Add(incoming) 48 | } 49 | } 50 | 51 | // RecordStream start record an incoming stream 52 | func (r *Recorder) RecordStream(incoming *IncomingStream) { 53 | 54 | for _, track := range incoming.GetTracks() { 55 | r.Record(track) 56 | } 57 | } 58 | 59 | // Stop stop the recorder 60 | func (r *Recorder) Stop() { 61 | 62 | if r.recorder == nil { 63 | return 64 | } 65 | 66 | for _, track := range r.tracks { 67 | track.Stop() 68 | } 69 | 70 | if r.refresher != nil { 71 | r.refresher.Stop() 72 | } 73 | 74 | r.recorder.Close() 75 | 76 | native.DeleteMP4RecorderFacade(r.recorder) 77 | 78 | r.refresher = nil 79 | r.recorder = nil 80 | } 81 | -------------------------------------------------------------------------------- /mediaframesession.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | native "github.com/notedit/media-server-go/wrapper" 8 | "github.com/notedit/sdp" 9 | ) 10 | 11 | type MediaFrameSession struct { 12 | sources map[string]native.RTPIncomingSourceGroup 13 | incoming *IncomingStreamTrack 14 | session native.MediaFrameSessionFacade 15 | } 16 | 17 | // NewMediaFrameSession create media frame session 18 | func NewMediaFrameSession(media *sdp.MediaInfo) *MediaFrameSession { 19 | 20 | mediaSession := &MediaFrameSession{} 21 | var mediaType native.MediaFrameType = 0 22 | if strings.ToLower(media.GetType()) == "video" { 23 | mediaType = 1 24 | } 25 | 26 | session := native.NewMediaFrameSessionFacade(mediaType) 27 | 28 | properties := native.NewPropertiesFacade() 29 | if media != nil { 30 | num := 0 31 | for _, codec := range media.GetCodecs() { 32 | item := fmt.Sprintf("codecs.%d", num) 33 | properties.SetPropertyStr(item+".codec", codec.GetCodec()) 34 | properties.SetPropertyInt(item+".pt", codec.GetType()) 35 | num = num + 1 36 | } 37 | properties.SetPropertyInt("codecs.length", num) 38 | } 39 | 40 | session.Init(properties) 41 | native.DeletePropertiesFacade(properties) 42 | 43 | sources := map[string]native.RTPIncomingSourceGroup{"": session.GetIncomingSourceGroup()} 44 | mediaSession.sources = sources 45 | mediaSession.session = session 46 | mediaSession.incoming = NewIncomingStreamTrack(media.GetType(), media.GetType(), native.RTPSessionToReceiver(session), sources) 47 | 48 | return mediaSession 49 | } 50 | 51 | // GetIncomingStreamTrack get incoming stream track 52 | func (s *MediaFrameSession) GetIncomingStreamTrack() *IncomingStreamTrack { 53 | return s.incoming 54 | } 55 | 56 | // Push push raw media frame 57 | func (s *MediaFrameSession) Push(rtp []byte) { 58 | if rtp == nil || len(rtp) == 0 { 59 | return 60 | } 61 | s.session.OnRTPPacket(&rtp[0], len(rtp)) 62 | } 63 | 64 | // Stop stop this 65 | func (s *MediaFrameSession) Stop() { 66 | 67 | if s.session == nil { 68 | return 69 | } 70 | 71 | if s.incoming != nil { 72 | s.incoming.Stop() 73 | } 74 | 75 | s.session.End() 76 | 77 | native.DeleteMediaFrameSessionFacade(s.session) 78 | 79 | s.session = nil 80 | } 81 | -------------------------------------------------------------------------------- /wrapper/mediaserver_wrap.h: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------------- 2 | * This file was automatically generated by SWIG (http://www.swig.org). 3 | * Version 4.0.0 4 | * 5 | * This file is not intended to be easily readable and contains a number of 6 | * coding conventions designed to improve portability and efficiency. Do not make 7 | * changes to this file unless you know what you are doing--modify the SWIG 8 | * interface file instead. 9 | * ----------------------------------------------------------------------------- */ 10 | 11 | // source: mediaserver.i 12 | 13 | #ifndef SWIG_native_WRAP_H_ 14 | #define SWIG_native_WRAP_H_ 15 | 16 | class Swig_memory; 17 | 18 | class SwigDirector_DTLSICETransportListener : public DTLSICETransportListener 19 | { 20 | public: 21 | SwigDirector_DTLSICETransportListener(int swig_p); 22 | virtual ~SwigDirector_DTLSICETransportListener(); 23 | void _swig_upcall_onDTLSStateChange(uint32_t state) { 24 | DTLSICETransportListener::onDTLSStateChange(state); 25 | } 26 | virtual void onDTLSStateChange(uint32_t state); 27 | private: 28 | intgo go_val; 29 | Swig_memory *swig_mem; 30 | }; 31 | 32 | class SwigDirector_SenderSideEstimatorListener : public SenderSideEstimatorListener 33 | { 34 | public: 35 | SwigDirector_SenderSideEstimatorListener(int swig_p); 36 | virtual ~SwigDirector_SenderSideEstimatorListener(); 37 | private: 38 | intgo go_val; 39 | Swig_memory *swig_mem; 40 | }; 41 | 42 | class SwigDirector_MediaFrameListenerFacade : public MediaFrameListenerFacade 43 | { 44 | public: 45 | SwigDirector_MediaFrameListenerFacade(int swig_p); 46 | virtual ~SwigDirector_MediaFrameListenerFacade(); 47 | void _swig_upcall_onMediaFrame(MediaFrame const &frame) { 48 | MediaFrameListenerFacade::onMediaFrame(frame); 49 | } 50 | virtual void onMediaFrame(MediaFrame const &frame); 51 | private: 52 | intgo go_val; 53 | Swig_memory *swig_mem; 54 | }; 55 | 56 | class SwigDirector_ActiveTrackListener : public ActiveTrackListener 57 | { 58 | public: 59 | SwigDirector_ActiveTrackListener(int swig_p); 60 | virtual ~SwigDirector_ActiveTrackListener(); 61 | void _swig_upcall_onActiveTrackchanged(uint32_t id) { 62 | ActiveTrackListener::onActiveTrackchanged(id); 63 | } 64 | virtual void onActiveTrackchanged(uint32_t id); 65 | private: 66 | intgo go_val; 67 | Swig_memory *swig_mem; 68 | }; 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # media-server-go 2 | 3 | [![Build Status](https://travis-ci.com/notedit/media-server-go.svg?branch=master)](https://travis-ci.com/notedit/media-server-go) 4 | 5 | WebRTC media server for go 6 | 7 | 8 | 9 | 10 | 11 | ## How to use 12 | 13 | [Read the Tutorial](https://github.com/notedit/media-server-go/blob/master/manual.md) 14 | 15 | 16 | Yon can see the demos from here [Demos](https://github.com/notedit/media-server-go-demo) 17 | 18 | 19 | 20 | ## Examples 21 | 22 | - [WebRTC-Broadcast](https://github.com/notedit/media-server-go-demo/tree/master/broadcast): WebRTC publish and play 23 | - [Raw-RTP-Input](https://github.com/notedit/media-server-go-demo/tree/master/raw-rtp-input): Send raw rtp data into webrtc 24 | - [WebRTC-Record](https://github.com/notedit/media-server-go-demo/tree/master/recording): WebRTC record 25 | - [RTMP-To-WebRTC](https://github.com/notedit/media-server-go-demo/tree/master/rtmp-to-webrtc): Rtmp to webrtc 26 | - [Server-To-Server](https://github.com/notedit/media-server-go-demo/tree/master/server-to-server): WebRTC server relay 27 | - [WebRTC-To-RTMP](https://github.com/notedit/media-server-go-demo/tree/master/webrtc-to-rtmp): WebRTC to rtmp 28 | - [WebRTC-To-HLS](https://github.com/notedit/media-server-go-demo/tree/master/webrtc-to-hls): WebRTC to hls 29 | 30 | 31 | 32 | ## Install 33 | 34 | 35 | `media-server-go` is not go getable, so you should clone it and build it yourself. 36 | 37 | You should install `libtool` and `autoconf` `automake` before you build 38 | 39 | 40 | On ubuntu 41 | ```sh 42 | apt install autoconf 43 | apt install libtool 44 | apt install automake 45 | ``` 46 | 47 | 48 | On macOS 49 | 50 | ```sh 51 | brew install libtool 52 | brew install autoconf 53 | brew install automake 54 | ``` 55 | 56 | 57 | Your compiler should support `c++17`, for linux, you should update your `gcc/g++` to `7.0+` 58 | 59 | for macos, clang should support `c++17`. 60 | 61 | 62 | ```sh 63 | sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 64 | sudo apt-get update -qq 65 | sudo apt-get install g++-7 66 | sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 90 67 | ``` 68 | 69 | 70 | ```sh 71 | git clone --recurse-submodules https://github.com/notedit/media-server-go.git 72 | 73 | cd media-server-go 74 | 75 | make 76 | 77 | go install 78 | 79 | ``` 80 | 81 | 82 | then you can use media-server-go in your project. 83 | 84 | 85 | 86 | 87 | ## Thanks 88 | 89 | - [Media Server](https://github.com/medooze/media-server) 90 | - [Media Server for Node.js](https://github.com/medooze/media-server-node) 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /mediaframemultiplexer.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import "C" 4 | import ( 5 | native "github.com/notedit/media-server-go/wrapper" 6 | ) 7 | 8 | // MediaStreamDuplicater we can make a copy of the incoming stream and callback the mediaframe data 9 | type MediaFrameMultiplexer struct { 10 | track *IncomingStreamTrack 11 | multiplexer native.MediaFrameMultiplexer 12 | listener mediaframeListener // used for native wrapper, see swig's doc 13 | 14 | mediaframeListener func([]byte, uint64) // used for outside 15 | } 16 | 17 | type mediaframeListener interface { 18 | native.MediaFrameListenerFacade 19 | deleteMediaFrameListener() 20 | } 21 | 22 | type goMediaFrameListener struct { 23 | native.MediaFrameListenerFacade 24 | } 25 | 26 | func (m *goMediaFrameListener) deleteMediaFrameListener() { 27 | native.DeleteDirectorMediaFrameListenerFacade(m.MediaFrameListenerFacade) 28 | } 29 | 30 | type overwrittenMediaFrameListener struct { 31 | p native.MediaFrameListenerFacade 32 | multiplexer *MediaFrameMultiplexer 33 | } 34 | 35 | func (p *overwrittenMediaFrameListener) OnMediaFrame(frame native.MediaFrame) { 36 | 37 | //if p.multiplexer != nil && p.multiplexer.mediaframeListener != nil { 38 | // buffer := C.GoBytes(unsafe.Pointer(frame.GetData()), C.int(frame.GetLength())) 39 | // if frame.GetType() == native.MediaFrameVideo { 40 | // data, err := annexbConvert(buffer) 41 | // if err == nil { 42 | // p.multiplexer.mediaframeListener(data, frame.GetTimeStamp()) 43 | // } else { 44 | // fmt.Println(err) 45 | // } 46 | // } else { 47 | // p.multiplexer.mediaframeListener(buffer, frame.GetTimeStamp()) 48 | // } 49 | // 50 | //} 51 | } 52 | 53 | // NewMediaStreamDuplicater duplicate this IncomingStreamTrack and callback the mediaframe 54 | func NewMediaFrameMultiplexer(track *IncomingStreamTrack) *MediaFrameMultiplexer { 55 | 56 | duplicater := &MediaFrameMultiplexer{} 57 | duplicater.track = track 58 | 59 | // We should make sure this source is the main source 60 | source := track.GetFirstEncoding().GetSource() 61 | duplicater.multiplexer = native.NewMediaFrameMultiplexer(source) 62 | 63 | listener := &overwrittenMediaFrameListener{ 64 | multiplexer: duplicater, 65 | } 66 | p := native.NewDirectorMediaFrameListenerFacade(listener) 67 | listener.p = p 68 | 69 | duplicater.listener = &goMediaFrameListener{MediaFrameListenerFacade: p} 70 | 71 | duplicater.multiplexer.AddMediaListener(duplicater.listener) 72 | 73 | return duplicater 74 | } 75 | 76 | // SetMediaFrameListener set outside mediaframe listener 77 | func (d *MediaFrameMultiplexer) SetMediaFrameListener(listener func([]byte, uint64)) { 78 | d.mediaframeListener = listener 79 | } 80 | 81 | // Stop stop this 82 | func (d *MediaFrameMultiplexer) Stop() { 83 | 84 | if d.track == nil { 85 | return 86 | } 87 | 88 | if d.listener != nil { 89 | d.multiplexer.RemoveMediaListener(d.listener) 90 | d.listener.deleteMediaFrameListener() 91 | } 92 | 93 | d.track = nil 94 | } 95 | -------------------------------------------------------------------------------- /incomingstreamtrackmirrored.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "github.com/notedit/media-server-go/wrapper" 5 | "github.com/notedit/sdp" 6 | ) 7 | 8 | 9 | 10 | type mirrorEncoding struct { 11 | id string 12 | source native.RTPIncomingMediaStreamMultiplexer 13 | depacketizer native.StreamTrackDepacketizer 14 | } 15 | 16 | 17 | type IncomingStreamTrackMirrored struct { 18 | track *IncomingStreamTrack 19 | receiver native.RTPReceiverFacade 20 | counter int 21 | encodings []*mirrorEncoding 22 | } 23 | 24 | func NewMirrorIncomingTrack(track *IncomingStreamTrack, timeService native.TimeService) *IncomingStreamTrackMirrored { 25 | 26 | mirror := &IncomingStreamTrackMirrored{} 27 | 28 | mirror.track = track 29 | mirror.receiver = track.receiver 30 | mirror.counter = 0 31 | mirror.encodings = []*mirrorEncoding{} 32 | 33 | for _,encoding := range track.GetEncodings() { 34 | source := native.NewRTPIncomingMediaStreamMultiplexer(encoding.source.GetMedia().GetSsrc(), timeService) 35 | encoding.source.AddListener(source) 36 | 37 | newEncoding := &mirrorEncoding{ 38 | id: encoding.id, 39 | source: source, 40 | depacketizer: native.NewStreamTrackDepacketizer(source.SwigGetRTPIncomingMediaStream()), 41 | } 42 | 43 | mirror.encodings = append(mirror.encodings, newEncoding) 44 | 45 | } 46 | 47 | mirror.track.Attached() 48 | 49 | return mirror 50 | } 51 | 52 | 53 | func (t *IncomingStreamTrackMirrored) GetStats() map[string]*IncomingAllStats { 54 | return t.track.GetStats() 55 | } 56 | 57 | 58 | func (t *IncomingStreamTrackMirrored) GetActiveLayers() *ActiveLayersInfo { 59 | return t.track.GetActiveLayers() 60 | } 61 | 62 | 63 | func (t *IncomingStreamTrackMirrored) GetID() string { 64 | return t.track.GetID() 65 | } 66 | 67 | func (t *IncomingStreamTrackMirrored) GetTrackInfo() *sdp.TrackInfo { 68 | return t.GetTrackInfo() 69 | } 70 | 71 | func (t *IncomingStreamTrackMirrored) GetSSRCs() { 72 | 73 | } 74 | 75 | func (t *IncomingStreamTrackMirrored) GetMedia() string { 76 | return t.track.GetMedia() 77 | } 78 | 79 | 80 | func (t *IncomingStreamTrackMirrored) Attached() bool { 81 | 82 | t.counter += 1 83 | 84 | if t.counter == 1 { 85 | return true 86 | } 87 | 88 | return false 89 | } 90 | 91 | 92 | func (t *IncomingStreamTrackMirrored) Refresh() { 93 | 94 | t.track.Refresh() 95 | } 96 | 97 | 98 | func (t *IncomingStreamTrackMirrored) Detached() bool { 99 | 100 | if t.counter == 0 { 101 | return true 102 | } 103 | 104 | t.counter -= 1 105 | 106 | if t.counter == 0 { 107 | return true 108 | } 109 | 110 | return false 111 | } 112 | 113 | 114 | func (t *IncomingStreamTrackMirrored) Stop() { 115 | 116 | if t.track == nil { 117 | return 118 | } 119 | 120 | for i,encoding := range t.track.GetEncodings() { 121 | mencoding := t.encodings[i] 122 | encoding.GetSource().RemoveListener(mencoding.source) 123 | native.DeleteRTPIncomingMediaStreamMultiplexer(t.encodings[i].source) 124 | mencoding.depacketizer.Stop() 125 | native.DeleteStreamTrackDepacketizer(mencoding.depacketizer) 126 | } 127 | 128 | 129 | t.track.Detached() 130 | 131 | t.encodings = nil 132 | 133 | t.track = nil 134 | 135 | t.receiver = nil 136 | 137 | } 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /packetizer/h264.go: -------------------------------------------------------------------------------- 1 | package packetizer 2 | 3 | 4 | type H264Packetier struct{} 5 | 6 | func (p *H264Packetier) Packetize(payload []byte, mtu int) (payloads [][]byte) { 7 | 8 | if payload == nil { 9 | return 10 | } 11 | 12 | nalus := splitnalus(payload) 13 | if nalus == nil { 14 | return 15 | } 16 | 17 | for _, nalu := range nalus { 18 | naluType := nalu[0] & 0x1F 19 | naluRefIdc := nalu[0] & 0x60 20 | 21 | if !(naluType < 9 && naluType > 4) { 22 | continue 23 | } 24 | 25 | if len(nalu) <= mtu { 26 | out := make([]byte, len(nalu)) 27 | copy(out, nalu) 28 | payloads = append(payloads, out) 29 | continue 30 | } 31 | 32 | // remove the fua header 33 | maxFragmentSize := mtu - 2 34 | 35 | naluData := nalu 36 | 37 | naluDataIndex := 1 38 | naluDataLength := len(nalu) - naluDataIndex 39 | naluDataRemaining := naluDataLength 40 | 41 | if min(maxFragmentSize, naluDataRemaining) <= 0 { 42 | continue 43 | } 44 | 45 | for naluDataRemaining > 0 { 46 | currentFragmentSize := min(maxFragmentSize, naluDataRemaining) 47 | out := make([]byte, 2+currentFragmentSize) 48 | 49 | // +---------------+ 50 | // |0|1|2|3|4|5|6|7| 51 | // +-+-+-+-+-+-+-+-+ 52 | // |F|NRI| Type | 53 | // +---------------+ 54 | out[0] = 28 55 | out[0] |= naluRefIdc 56 | 57 | // +---------------+ 58 | //|0|1|2|3|4|5|6|7| 59 | //+-+-+-+-+-+-+-+-+ 60 | //|S|E|R| Type | 61 | //+---------------+ 62 | 63 | out[1] = naluType 64 | if naluDataRemaining == naluDataLength { 65 | // Set start bit 66 | out[1] |= 1 << 7 67 | } else if naluDataRemaining-currentFragmentSize == 0 { 68 | // Set end bit 69 | out[1] |= 1 << 6 70 | } 71 | 72 | copy(out[2:], naluData[naluDataIndex:naluDataIndex+currentFragmentSize]) 73 | payloads = append(payloads, out) 74 | 75 | naluDataRemaining -= currentFragmentSize 76 | naluDataIndex += currentFragmentSize 77 | } 78 | 79 | } 80 | 81 | return payloads 82 | } 83 | 84 | func splitnalus(b []byte) (nalus [][]byte) { 85 | 86 | if len(b) < 4 { 87 | return 88 | } 89 | 90 | val3 := u24be(b) 91 | val4 := u32be(b) 92 | 93 | if val3 == 1 || val4 == 1 { 94 | _val3 := val3 95 | _val4 := val4 96 | start := 0 97 | pos := 0 98 | for { 99 | if start != pos { 100 | nalus = append(nalus, b[start:pos]) 101 | } 102 | if _val3 == 1 { 103 | pos += 3 104 | } else if _val4 == 1 { 105 | pos += 4 106 | } 107 | start = pos 108 | if start == len(b) { 109 | break 110 | } 111 | _val3 = 0 112 | _val4 = 0 113 | 114 | for pos < len(b) { 115 | if pos+2 < len(b) && b[pos] == 0 { 116 | _val3 = u24be(b[pos:]) 117 | if _val3 == 0 { 118 | if pos+3 < len(b) { 119 | _val4 = uint32(b[pos+3]) 120 | if _val4 == 1 { 121 | break 122 | } 123 | } 124 | } else if _val3 == 1 { 125 | break 126 | } 127 | pos++ 128 | } else { 129 | pos++ 130 | } 131 | } 132 | 133 | } 134 | } 135 | return 136 | } 137 | 138 | func min(a, b int) int { 139 | if a < b { 140 | return a 141 | } 142 | return b 143 | } 144 | 145 | func u32be(b []byte) (i uint32) { 146 | i = uint32(b[0]) 147 | i <<= 8 148 | i |= uint32(b[1]) 149 | i <<= 8 150 | i |= uint32(b[2]) 151 | i <<= 8 152 | i |= uint32(b[3]) 153 | return 154 | } 155 | 156 | func u24be(b []byte) (i uint32) { 157 | i = uint32(b[0]) 158 | i <<= 8 159 | i |= uint32(b[1]) 160 | i <<= 8 161 | i |= uint32(b[2]) 162 | return 163 | } -------------------------------------------------------------------------------- /manual.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | This document will show you how to setup and endpoint and transport manually. 3 | 4 | ## Initialization 5 | 6 | First import both media server go and sdp module: 7 | 8 | ```go 9 | import "github.com/notedit/media-server-go" 10 | ``` 11 | 12 | 13 | Then you need to create an Endpoint, which will create an UDP socket for receiving connection. You need to pass the `ip` address in which this Enpoint will be accesible by the WebRTC clients. This is typically the public IP address of the server, ans will be used on the ICE candidates sent to the browser on the SDP. 14 | 15 | You can also pass a `port` with a `ip`, see `NewEndpointWithPort(ip string, port int)`, this endpoint will listen on this port. 16 | 17 | ```go 18 | //Create UDP server endpoint 19 | endpoint := mediaserver.NewEndpoint("127.0.0.1") 20 | // Or 21 | endpoint := mediaserver.NewEndpointWithPort("127.0.0.1", 50000) 22 | ``` 23 | 24 | Now you are ready to connect to your server. 25 | 26 | ## Connect a client 27 | 28 | On your browser, create an SDP offer and sent it to your server (via websockets for example). Once you have it, you will have to parse it to extract the requried information. 29 | With that information, you can create an ICE+DTLS transport on the `Endpoint`. 30 | 31 | ```go 32 | //Process the sdp 33 | offer, err := sdp.Parse(offerStr) 34 | 35 | //Create an DTLS ICE transport in that enpoint 36 | transport = endpoint.CreateTransport(offer, nil) 37 | 38 | ``` 39 | 40 | Now set the RTP remote properties for both audio and video: 41 | 42 | ```go 43 | //Set RTP remote properties 44 | transport.SetRemoteProperties(offer.GetMedia("audio"), offer.GetMedia("video")) 45 | ``` 46 | 47 | You can start creating the answer now. First get the ICE and DTLS info from the `Transport` and the ICE candidate into from the `Endpoint` 48 | 49 | ```go 50 | //Get local DTLS and ICE info 51 | ice := transport.GetLocalICEInfo() 52 | dtls := transport.GetLocalDTLSInfo() 53 | candidates := endpoint.GetLocalCandidates() 54 | 55 | answer := sdp.NewSDPInfo() 56 | 57 | //Add ice and dtls info 58 | answer.SetDTLS(dtls) 59 | answer.SetICE(ice) 60 | //Add candidates 61 | answer.AddCandidates(candidates) 62 | ``` 63 | 64 | Choose your codecs and set RTP parameters to answer the offer: 65 | 66 | ```go 67 | //Get remote audio m-line info 68 | if offer.GetMedia("audio") != nil { 69 | audioMedia := offer.GetMedia("audio").AnswerCapability(audioCapability) 70 | answer.AddMedia(audioMedia) 71 | } 72 | 73 | //Get remote video m-line info 74 | const videoOffer = offer.getMedia("video"); 75 | 76 | //If offer had video 77 | if offer.GetMedia("video") != nil { 78 | videoMedia := offer.GetMedia("video").AnswerCapability(videoCapability) 79 | answer.AddMedia(videoMedia) 80 | } 81 | ``` 82 | 83 | Set the our negotiated RTP properties on the transport 84 | 85 | ```go 86 | //Set RTP local properties 87 | transport.SetLocalProperties(answer.GetMedia("audio"), answer.GetMedia("video")) 88 | ``` 89 | 90 | ## Stream management 91 | 92 | You need to process the stream offered by the client, so extract the stream info from the SDP offer, and create an `IncomingStream` object. 93 | 94 | ```go 95 | //Get stream 96 | for _, stream := range offer.GetStreams() { 97 | 98 | incomingStream := transport.CreateIncomingStream(stream) 99 | outgoingStream := transport.CreateOutgoingStream2(stream.Clone()) 100 | 101 | outgoingStream.AttachTo(incomingStream) 102 | 103 | answer.AddStream(outgoingStream.GetStreamInfo()) 104 | } 105 | //Create the remote stream into the transport 106 | const incomingStream = transport.createIncomingStream(offered); 107 | ``` 108 | 109 | Now, for example, create an outgoing stream, and add it to the answer so the browser is aware of it. 110 | 111 | ```go 112 | //Create new local stream 113 | outgoingStream := transport.CreateOutgoingStream2(stream.Clone()) 114 | //Add local stream info it to the answer 115 | answer.AddStream(outgoingStream.GetStreamInfo()) 116 | ``` 117 | 118 | You can attach an `OutgoingStream` to an `IncomingStream`, this will create a `Transponder` array that will forward the incoming data to the ougoing stream, it will allow you also to apply transoformations to it (like SVC layer selection). 119 | 120 | In this case, as you are attaching an incoming stream to an outgoing stream from the same client, you will get audio and video loopback on the client. 121 | 122 | ```go 123 | //Copy incoming data from the remote stream to the local one 124 | outgoingStream.AttachTo(incomingStream) 125 | ``` 126 | 127 | You can now send answer the SDP to the client. 128 | ```go 129 | //Get answer SDP 130 | const str = answer.toString() 131 | ``` -------------------------------------------------------------------------------- /streamersession.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gofrs/uuid" 8 | "github.com/notedit/sdp" 9 | 10 | native "github.com/notedit/media-server-go/wrapper" 11 | ) 12 | 13 | // StreamerSession represent a rtp session 14 | type StreamerSession struct { 15 | id string 16 | local bool 17 | port int 18 | ip string 19 | incoming *IncomingStreamTrack 20 | outgoing *OutgoingStreamTrack 21 | session native.RTPSessionFacade 22 | onStopListeners []func() 23 | } 24 | 25 | // NewStreamerSession new StreamerSession with auto selectd port 26 | func NewStreamerSession(media *sdp.MediaInfo) *StreamerSession { 27 | 28 | streamerSession := &StreamerSession{} 29 | var mediaType native.MediaFrameType = 0 30 | if strings.ToLower(media.GetType()) == "video" { 31 | mediaType = 1 32 | } 33 | session := native.NewRTPSessionFacade(mediaType) 34 | 35 | streamerSession.id = uuid.Must(uuid.NewV4()).String() 36 | 37 | properties := native.NewPropertiesFacade() 38 | 39 | if media != nil { 40 | num := 0 41 | for _, codec := range media.GetCodecs() { 42 | item := fmt.Sprintf("codecs.%d", num) 43 | properties.SetPropertyStr(item+".codec", codec.GetCodec()) 44 | properties.SetPropertyInt(item+".pt", codec.GetType()) 45 | if codec.HasRTX() { 46 | properties.SetPropertyInt(item+".rtx", codec.GetRTX()) 47 | } 48 | num = num + 1 49 | } 50 | properties.SetPropertyInt("codecs.length", num) 51 | } 52 | 53 | session.Init(properties) 54 | 55 | native.DeletePropertiesFacade(properties) 56 | 57 | streamerSession.session = session 58 | 59 | streamerSession.incoming = NewIncomingStreamTrack(media.GetType(), media.GetType(), native.SessionToReceiver(session), map[string]native.RTPIncomingSourceGroup{"": session.GetIncomingSourceGroup()}) 60 | 61 | streamerSession.outgoing = newOutgoingStreamTrack(media.GetType(), media.GetType(), native.SessionToSender(session), session.GetOutgoingSourceGroup()) 62 | 63 | streamerSession.onStopListeners = make([]func(), 0) 64 | 65 | return streamerSession 66 | } 67 | 68 | // NewStreamerSessionWithLocalPort create streamer session with pre selected port 69 | func NewStreamerSessionWithLocalPort(port int, media *sdp.MediaInfo) *StreamerSession { 70 | 71 | streamerSession := &StreamerSession{} 72 | var mediaType native.MediaFrameType = 0 73 | if strings.ToLower(media.GetType()) == "video" { 74 | mediaType = 1 75 | } 76 | session := native.NewRTPSessionFacade(mediaType) 77 | 78 | streamerSession.id = uuid.Must(uuid.NewV4()).String() 79 | 80 | properties := native.NewPropertiesFacade() 81 | 82 | if media != nil { 83 | num := 0 84 | for _, codec := range media.GetCodecs() { 85 | item := fmt.Sprintf("codecs.%d", num) 86 | properties.SetPropertyStr(item+".codec", codec.GetCodec()) 87 | properties.SetPropertyInt(item+".pt", codec.GetType()) 88 | if codec.HasRTX() { 89 | properties.SetPropertyInt(item+".rtx", codec.GetRTX()) 90 | } 91 | num = num + 1 92 | } 93 | properties.SetPropertyInt("codecs.length", num) 94 | } 95 | 96 | session.SetLocalPort(port) 97 | 98 | session.Init(properties) 99 | 100 | native.DeletePropertiesFacade(properties) 101 | 102 | streamerSession.session = session 103 | 104 | streamerSession.incoming = NewIncomingStreamTrack(media.GetType(), media.GetType(), native.SessionToReceiver(session), map[string]native.RTPIncomingSourceGroup{"": session.GetIncomingSourceGroup()}) 105 | 106 | streamerSession.outgoing = newOutgoingStreamTrack(media.GetType(), media.GetType(), native.SessionToSender(session), session.GetOutgoingSourceGroup()) 107 | 108 | streamerSession.onStopListeners = make([]func(), 0) 109 | 110 | return streamerSession 111 | } 112 | 113 | // GetID get id 114 | func (s *StreamerSession) GetID() string { 115 | return s.id 116 | } 117 | 118 | func (s *StreamerSession) GetLocalPort() int { 119 | return s.session.GetLocalPort() 120 | } 121 | 122 | func (s *StreamerSession) SetRemotePort(ip string, port int) { 123 | s.session.SetRemotePort(ip, port) 124 | } 125 | 126 | // GetIncomingStreamTrack get asso incoming track, 127 | func (s *StreamerSession) GetIncomingStreamTrack() *IncomingStreamTrack { 128 | return s.incoming 129 | } 130 | 131 | // GetOutgoingStreamTrack get asso outgoing track, 132 | func (s *StreamerSession) GetOutgoingStreamTrack() *OutgoingStreamTrack { 133 | return s.outgoing 134 | } 135 | 136 | // Stop it 137 | func (s *StreamerSession) Stop() { 138 | 139 | if s.session == nil { 140 | return 141 | } 142 | 143 | if s.incoming != nil { 144 | s.incoming.Stop() 145 | } 146 | 147 | if s.outgoing != nil { 148 | s.outgoing.Stop() 149 | } 150 | 151 | s.session.End() 152 | 153 | native.DeleteRTPSessionFacade(s.session) 154 | 155 | s.session = nil 156 | } 157 | -------------------------------------------------------------------------------- /endpoint.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "sync" 5 | 6 | native "github.com/notedit/media-server-go/wrapper" 7 | "github.com/notedit/sdp" 8 | ) 9 | 10 | // Endpoint is an endpoint represent an UDP server socket. 11 | // The endpoint will process STUN requests in order to be able to associate the remote ip:port with the registered transport and forward any further data comming from that transport. 12 | // Being a server it is ICE-lite. 13 | type Endpoint struct { 14 | ip string 15 | bundle native.RTPBundleTransport 16 | candidate *sdp.CandidateInfo 17 | mirroredStreams map[string]*IncomingStream 18 | mirroredTracks map[string]*IncomingStreamTrack 19 | fingerprint string 20 | sync.Mutex 21 | } 22 | 23 | // NewEndpoint create a new endpoint with given ip 24 | func NewEndpoint(ip string) *Endpoint { 25 | endpoint := &Endpoint{} 26 | endpoint.bundle = native.NewRTPBundleTransport() 27 | endpoint.bundle.Init() 28 | endpoint.fingerprint = native.MediaServerGetFingerprint() 29 | endpoint.mirroredStreams = make(map[string]*IncomingStream) 30 | endpoint.mirroredTracks = make(map[string]*IncomingStreamTrack) 31 | endpoint.candidate = sdp.NewCandidateInfo("1", 1, "UDP", 33554431, ip, endpoint.bundle.GetLocalPort(), "host", "", 0) 32 | return endpoint 33 | } 34 | 35 | // NewEndpointWithPort create a new endpint with given ip and port 36 | func NewEndpointWithPort(ip string, port int) *Endpoint { 37 | endpoint := &Endpoint{} 38 | endpoint.bundle = native.NewRTPBundleTransport() 39 | endpoint.bundle.Init(port) 40 | endpoint.fingerprint = native.MediaServerGetFingerprint() 41 | endpoint.candidate = sdp.NewCandidateInfo("1", 1, "UDP", 33554431, ip, endpoint.bundle.GetLocalPort(), "host", "", 0) 42 | return endpoint 43 | } 44 | 45 | //SetAffinity Set cpu affinity 46 | func (e *Endpoint) SetAffinity(cpu int) { 47 | e.bundle.SetAffinity(cpu) 48 | } 49 | 50 | // CreateTransport create a new transport object and register it with the remote ICE username and password 51 | // disableSTUNKeepAlive - Disable ICE/STUN keep alives, required for server to server transports, set this to false if you do not how to use it 52 | func (e *Endpoint) CreateTransport(remoteSdp *sdp.SDPInfo, localSdp *sdp.SDPInfo, options ...bool) *Transport { 53 | 54 | var localIce *sdp.ICEInfo 55 | var localDtls *sdp.DTLSInfo 56 | var localCandidates []*sdp.CandidateInfo 57 | 58 | if localSdp == nil { 59 | localIce = sdp.ICEInfoGenerate(true) 60 | localDtls = sdp.NewDTLSInfo(remoteSdp.GetDTLS().GetSetup().Reverse(), "sha-256", e.fingerprint) 61 | localCandidates = []*sdp.CandidateInfo{e.candidate} 62 | } else { 63 | localIce = localSdp.GetICE().Clone() 64 | localDtls = localSdp.GetDTLS().Clone() 65 | localCandidates = localSdp.GetCandidates() 66 | } 67 | 68 | remoteIce := remoteSdp.GetICE().Clone() 69 | remoteDtls := remoteSdp.GetDTLS().Clone() 70 | remoteCandidates := remoteSdp.GetCandidates() 71 | 72 | localIce.SetLite(true) 73 | localIce.SetEndOfCandidate(true) 74 | 75 | disableSTUNKeepAlive := false 76 | 77 | if len(options) > 0 { 78 | disableSTUNKeepAlive = options[0] 79 | } 80 | 81 | transport := NewTransport(e.bundle, remoteIce, remoteDtls, remoteCandidates, 82 | localIce, localDtls, localCandidates, disableSTUNKeepAlive) 83 | 84 | return transport 85 | } 86 | 87 | // GetLocalCandidates Get local ICE candidates for this endpoint. It will be shared by all the transport associated to this endpoint. 88 | func (e *Endpoint) GetLocalCandidates() []*sdp.CandidateInfo { 89 | return []*sdp.CandidateInfo{e.candidate} 90 | } 91 | 92 | // GetDTLSFingerprint Get local DTLS fingerprint for this endpoint. It will be shared by all the transport associated to this endpoint 93 | func (e *Endpoint) GetDTLSFingerprint() string { 94 | return e.fingerprint 95 | } 96 | 97 | // CreateOffer create offer based on audio and video capability 98 | // It generates a random ICE username and password and gets endpoint fingerprint 99 | func (e *Endpoint) CreateOffer(video *sdp.Capability, audio *sdp.Capability) *sdp.SDPInfo { 100 | 101 | dtls := sdp.NewDTLSInfo(sdp.SETUPACTPASS, "sha-256", e.fingerprint) 102 | 103 | ice := sdp.GenerateICEInfo(true) 104 | 105 | candidates := e.GetLocalCandidates() 106 | 107 | capabilities := make(map[string]*sdp.Capability) 108 | 109 | if video != nil { 110 | capabilities["video"] = video 111 | } 112 | 113 | if audio != nil { 114 | capabilities["audio"] = audio 115 | } 116 | 117 | return sdp.Create(ice, dtls, candidates, capabilities) 118 | } 119 | 120 | 121 | // Stop stop the endpoint UDP server and terminate any associated transport 122 | func (e *Endpoint) Stop() { 123 | 124 | if e.bundle == nil { 125 | return 126 | } 127 | 128 | e.bundle.End() 129 | 130 | native.DeleteRTPBundleTransport(e.bundle) 131 | 132 | e.bundle = nil 133 | 134 | } 135 | -------------------------------------------------------------------------------- /outgoingstreamtrack.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "time" 5 | 6 | native "github.com/notedit/media-server-go/wrapper" 7 | "github.com/notedit/sdp" 8 | ) 9 | 10 | // OutgoingStreamTrack Audio or Video track of a media stream sent to a remote peer 11 | type OutgoingStreamTrack struct { 12 | id string 13 | media string 14 | muted bool 15 | sender native.RTPSenderFacade 16 | source native.RTPOutgoingSourceGroup 17 | transpoder *Transponder 18 | trackInfo *sdp.TrackInfo 19 | statss *OutgoingStatss 20 | onMuteListeners []func(bool) 21 | onStopListeners []func() 22 | // todo outercallback 23 | } 24 | 25 | // OutgoingStats stats info 26 | type OutgoingStats struct { 27 | NumPackets uint 28 | NumRTCPPackets uint 29 | TotalBytes uint 30 | TotalRTCPBytes uint 31 | Bitrate uint 32 | } 33 | 34 | // OutgoingStatss stats info 35 | type OutgoingStatss struct { 36 | Media *OutgoingStats 37 | Rtx *OutgoingStats 38 | Fec *OutgoingStats 39 | timestamp int64 40 | } 41 | 42 | func getStatsFromOutgoingSource(source native.RTPOutgoingSource) *OutgoingStats { 43 | 44 | stats := &OutgoingStats{ 45 | NumPackets: source.GetNumPackets(), 46 | NumRTCPPackets: source.GetNumRTCPPackets(), 47 | TotalBytes: source.GetTotalBytes(), 48 | TotalRTCPBytes: source.GetTotalRTCPBytes(), 49 | Bitrate: source.GetBitrate(), 50 | } 51 | 52 | return stats 53 | } 54 | 55 | // NewOutgoingStreamTrack create outgoing stream track 56 | func newOutgoingStreamTrack(media string, id string, sender native.RTPSenderFacade, source native.RTPOutgoingSourceGroup) *OutgoingStreamTrack { 57 | 58 | track := &OutgoingStreamTrack{} 59 | track.id = id 60 | track.media = media 61 | track.sender = sender 62 | track.muted = false 63 | track.source = source 64 | track.trackInfo = sdp.NewTrackInfo(id, media) 65 | 66 | track.trackInfo.AddSSRC(source.GetMedia().GetSsrc()) 67 | 68 | if source.GetRtx().GetSsrc() > 0 { 69 | track.trackInfo.AddSSRC(source.GetRtx().GetSsrc()) 70 | } 71 | 72 | if source.GetFec().GetSsrc() > 0 { 73 | track.trackInfo.AddSSRC(source.GetFec().GetSsrc()) 74 | } 75 | 76 | if source.GetRtx().GetSsrc() > 0 { 77 | sourceGroup := sdp.NewSourceGroupInfo("FID", []uint{source.GetMedia().GetSsrc(), source.GetRtx().GetSsrc()}) 78 | track.trackInfo.AddSourceGroup(sourceGroup) 79 | } 80 | 81 | if source.GetFec().GetSsrc() > 0 { 82 | sourceGroup := sdp.NewSourceGroupInfo("FEC-FR", []uint{source.GetMedia().GetSsrc(), source.GetFec().GetSsrc()}) 83 | track.trackInfo.AddSourceGroup(sourceGroup) 84 | } 85 | 86 | 87 | track.onMuteListeners = make([]func(bool), 0) 88 | track.onStopListeners = make([]func(), 0) 89 | 90 | return track 91 | } 92 | 93 | // GetID get track id 94 | func (o *OutgoingStreamTrack) GetID() string { 95 | return o.id 96 | } 97 | 98 | // GetMedia get media type 99 | func (o *OutgoingStreamTrack) GetMedia() string { 100 | return o.media 101 | } 102 | 103 | // GetTrackInfo get track info 104 | func (o *OutgoingStreamTrack) GetTrackInfo() *sdp.TrackInfo { 105 | return o.trackInfo 106 | } 107 | 108 | // GetStats get stats info 109 | func (o *OutgoingStreamTrack) GetStats() *OutgoingStatss { 110 | 111 | if o.statss == nil { 112 | o.statss = &OutgoingStatss{} 113 | } 114 | 115 | if time.Now().UnixNano()-o.statss.timestamp > 200000000 { 116 | o.statss.Media = getStatsFromOutgoingSource(o.source.GetMedia()) 117 | o.statss.Rtx = getStatsFromOutgoingSource(o.source.GetRtx()) 118 | o.statss.Fec = getStatsFromOutgoingSource(o.source.GetFec()) 119 | o.statss.timestamp = time.Now().UnixNano() 120 | } 121 | 122 | return o.statss 123 | } 124 | 125 | // GetSSRCs get ssrcs map 126 | func (o *OutgoingStreamTrack) GetSSRCs() map[string]native.RTPOutgoingSource { 127 | 128 | return map[string]native.RTPOutgoingSource{ 129 | "media": o.source.GetMedia(), 130 | "rtx": o.source.GetRtx(), 131 | "fec": o.source.GetFec(), 132 | } 133 | } 134 | 135 | // IsMuted Check if the track is muted or not 136 | func (o *OutgoingStreamTrack) IsMuted() bool { 137 | return o.muted 138 | } 139 | 140 | // Mute Mute/Unmute the track 141 | func (o *OutgoingStreamTrack) Mute(muting bool) { 142 | 143 | if o.transpoder != nil { 144 | o.transpoder.Mute(muting) 145 | } 146 | 147 | if o.muted != muting { 148 | o.muted = muting 149 | 150 | for _, mutefunc := range o.onMuteListeners { 151 | mutefunc(muting) 152 | } 153 | } 154 | } 155 | 156 | // AttachTo Listen media from the incoming stream track and send it to the remote peer of the associated transport 157 | func (o *OutgoingStreamTrack) AttachTo(incomingTrack *IncomingStreamTrack) *Transponder { 158 | 159 | // detach first 160 | o.Detach() 161 | 162 | transponder := native.NewRTPStreamTransponderFacade(o.source, o.sender) 163 | 164 | o.transpoder = NewTransponder(transponder) 165 | 166 | if o.muted { 167 | o.transpoder.Mute(o.muted) 168 | } 169 | 170 | o.transpoder.SetIncomingTrack(incomingTrack) 171 | 172 | return o.transpoder 173 | } 174 | 175 | // Detach Stop forwarding any previous attached track 176 | func (o *OutgoingStreamTrack) Detach() { 177 | 178 | if o.transpoder == nil { 179 | return 180 | } 181 | 182 | o.transpoder.Stop() 183 | 184 | o.transpoder = nil 185 | } 186 | 187 | // GetTransponder Get attached transpoder for this track 188 | func (o *OutgoingStreamTrack) GetTransponder() *Transponder { 189 | return o.transpoder 190 | } 191 | 192 | func (o *OutgoingStreamTrack) OnMute(mute func(bool)) { 193 | o.onMuteListeners = append(o.onMuteListeners, mute) 194 | } 195 | 196 | // Stop Removes the track from the outgoing stream and also detaches from any attached incoming track 197 | func (o *OutgoingStreamTrack) Stop() { 198 | 199 | if o.sender == nil { 200 | return 201 | } 202 | 203 | if o.transpoder != nil { // maybe = nil at onTransponderStopped 204 | o.transpoder.Stop() 205 | o.transpoder = nil 206 | } 207 | 208 | native.DeleteRTPSenderFacade(o.sender) 209 | o.sender = nil 210 | } 211 | 212 | func (o *OutgoingStreamTrack) DeleteOutgoingSourceGroup(transport native.DTLSICETransport) { 213 | if o.source != nil { 214 | transport.RemoveOutgoingSourceGroup(o.source) 215 | native.DeleteRTPOutgoingSourceGroup(o.source) 216 | o.source = nil 217 | } 218 | } 219 | 220 | -------------------------------------------------------------------------------- /outgoingstream.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | 7 | native "github.com/notedit/media-server-go/wrapper" 8 | "github.com/notedit/sdp" 9 | ) 10 | 11 | // OutgoingStream represent the media stream sent to a remote peer 12 | type OutgoingStream struct { 13 | id string 14 | transport native.DTLSICETransport 15 | info *sdp.StreamInfo 16 | muted bool 17 | tracks map[string]*OutgoingStreamTrack 18 | onStopListeners []func() 19 | onAddTrackListeners []func(*OutgoingStreamTrack) 20 | l sync.Mutex 21 | } 22 | 23 | // NewOutgoingStream create outgoing stream 24 | func NewOutgoingStream(transport native.DTLSICETransport, info *sdp.StreamInfo) *OutgoingStream { 25 | stream := new(OutgoingStream) 26 | 27 | stream.id = info.GetID() 28 | stream.transport = transport 29 | stream.info = info 30 | stream.tracks = make(map[string]*OutgoingStreamTrack) 31 | 32 | for _, track := range info.GetTracks() { 33 | stream.CreateTrack(track) 34 | } 35 | 36 | stream.onStopListeners = make([]func(), 0) 37 | stream.onAddTrackListeners = make([]func(*OutgoingStreamTrack), 0) 38 | 39 | return stream 40 | } 41 | 42 | // GetID get id 43 | func (o *OutgoingStream) GetID() string { 44 | return o.id 45 | } 46 | 47 | // GetStats Get statistics for all tracks in the stream 48 | func (o *OutgoingStream) GetStats() map[string]*OutgoingStatss { 49 | 50 | stats := map[string]*OutgoingStatss{} 51 | for _, track := range o.tracks { 52 | stats[track.GetID()] = track.GetStats() 53 | } 54 | return stats 55 | } 56 | 57 | // IsMuted Check if the stream is muted or not 58 | func (o *OutgoingStream) IsMuted() bool { 59 | return o.muted 60 | } 61 | 62 | // Mute Mute/Unmute this stream and all the tracks in it 63 | func (o *OutgoingStream) Mute(muting bool) { 64 | 65 | for _, track := range o.tracks { 66 | track.Mute(muting) 67 | } 68 | 69 | if o.muted != muting { 70 | o.muted = muting 71 | } 72 | } 73 | 74 | // AttachTo Listen media from the incoming stream and send it to the remote peer of the associated transport 75 | func (o *OutgoingStream) AttachTo(incomingStream *IncomingStream) []*Transponder { 76 | 77 | o.Detach() 78 | transponders := []*Transponder{} 79 | audios := o.GetAudioTracks() 80 | if len(audios) > 0 { 81 | index := len(audios) 82 | tracks := incomingStream.GetAudioTracks() 83 | if index < len(tracks) { 84 | index = len(tracks) 85 | } 86 | 87 | for i, track := range tracks { 88 | if i < index { 89 | transponders = append(transponders, audios[i].AttachTo(track)) 90 | } 91 | } 92 | } 93 | 94 | videos := o.GetVideoTracks() 95 | if len(videos) > 0 { 96 | index := len(videos) 97 | tracks := incomingStream.GetVideoTracks() 98 | if index < len(tracks) { 99 | index = len(tracks) 100 | } 101 | for i, track := range tracks { 102 | if i < index { 103 | transponders = append(transponders, videos[i].AttachTo(track)) 104 | } 105 | } 106 | } 107 | 108 | return transponders 109 | } 110 | 111 | // Detach Stop listening for media 112 | func (o *OutgoingStream) Detach() { 113 | 114 | o.l.Lock() 115 | defer o.l.Unlock() 116 | for _, track := range o.tracks { 117 | track.Detach() 118 | } 119 | } 120 | 121 | // GetStreamInfo get the stream info 122 | func (o *OutgoingStream) GetStreamInfo() *sdp.StreamInfo { 123 | return o.info 124 | } 125 | 126 | // GetTrack get one track 127 | func (o *OutgoingStream) GetTrack(trackID string) *OutgoingStreamTrack { 128 | o.l.Lock() 129 | defer o.l.Unlock() 130 | return o.tracks[trackID] 131 | } 132 | 133 | // GetTracks get all the tracks 134 | func (o *OutgoingStream) GetTracks() []*OutgoingStreamTrack { 135 | o.l.Lock() 136 | defer o.l.Unlock() 137 | tracks := []*OutgoingStreamTrack{} 138 | for _, track := range o.tracks { 139 | tracks = append(tracks, track) 140 | } 141 | return tracks 142 | } 143 | 144 | // GetAudioTracks Get an array of the media stream audio tracks 145 | func (o *OutgoingStream) GetAudioTracks() []*OutgoingStreamTrack { 146 | o.l.Lock() 147 | defer o.l.Unlock() 148 | audioTracks := []*OutgoingStreamTrack{} 149 | for _, track := range o.tracks { 150 | if strings.ToLower(track.GetMedia()) == "audio" { 151 | audioTracks = append(audioTracks, track) 152 | } 153 | } 154 | return audioTracks 155 | } 156 | 157 | // GetVideoTracks Get an array of the media stream video tracks 158 | func (o *OutgoingStream) GetVideoTracks() []*OutgoingStreamTrack { 159 | o.l.Lock() 160 | defer o.l.Unlock() 161 | videoTracks := []*OutgoingStreamTrack{} 162 | for _, track := range o.tracks { 163 | if strings.ToLower(track.GetMedia()) == "video" { 164 | videoTracks = append(videoTracks, track) 165 | } 166 | } 167 | return videoTracks 168 | } 169 | 170 | // AddTrack add one outgoing track 171 | func (o *OutgoingStream) AddTrack(track *OutgoingStreamTrack) { 172 | o.l.Lock() 173 | defer o.l.Unlock() 174 | 175 | if _, ok := o.tracks[track.GetID()]; ok { 176 | return 177 | } 178 | o.tracks[track.GetID()] = track 179 | } 180 | 181 | func (o *OutgoingStream) RemoveTrack(track *OutgoingStreamTrack) { 182 | o.l.Lock() 183 | defer o.l.Unlock() 184 | 185 | delete(o.tracks, track.GetID()) 186 | } 187 | 188 | // CreateTrack Create new track from a TrackInfo object and add it to this stream 189 | func (o *OutgoingStream) CreateTrack(track *sdp.TrackInfo) *OutgoingStreamTrack { 190 | 191 | var mediaType native.MediaFrameType = 0 192 | if track.GetMedia() == "video" { 193 | mediaType = 1 194 | } 195 | 196 | source := native.NewRTPOutgoingSourceGroup(mediaType) 197 | 198 | source.GetMedia().SetSsrc(track.GetSSRCS()[0]) 199 | 200 | fid := track.GetSourceGroup("FID") 201 | fec_fr := track.GetSourceGroup("FEC-FR") 202 | 203 | if fid != nil { 204 | source.GetRtx().SetSsrc(fid.GetSSRCs()[1]) 205 | } else { 206 | source.GetRtx().SetSsrc(0) 207 | } 208 | 209 | if fec_fr != nil { 210 | source.GetFec().SetSsrc(fec_fr.GetSSRCs()[1]) 211 | } else { 212 | source.GetFec().SetSsrc(0) 213 | } 214 | 215 | if _, ok := o.tracks[track.GetID()]; ok { 216 | return nil 217 | } 218 | 219 | o.transport.AddOutgoingSourceGroup(source) 220 | 221 | outgoingTrack := newOutgoingStreamTrack(track.GetMedia(), track.GetID(), native.TransportToSender(o.transport), source) 222 | 223 | // TODO 224 | // runtime.SetFinalizer(source, func(source native.RTPOutgoingSourceGroup) { 225 | // o.transport.RemoveOutgoingSourceGroup(source) 226 | // }) 227 | 228 | o.l.Lock() 229 | o.tracks[outgoingTrack.GetID()] = outgoingTrack 230 | o.l.Unlock() 231 | 232 | for _, addTrackFunc := range o.onAddTrackListeners { 233 | addTrackFunc(outgoingTrack) 234 | } 235 | 236 | return outgoingTrack 237 | } 238 | 239 | // OnTrack new outgoing track listener 240 | func (o *OutgoingStream) OnTrack(listener func(*OutgoingStreamTrack)) { 241 | o.onAddTrackListeners = append(o.onAddTrackListeners, listener) 242 | } 243 | 244 | 245 | // Stop stop the remote stream 246 | func (o *OutgoingStream) Stop() { 247 | 248 | if o.transport == nil { 249 | return 250 | } 251 | 252 | for _, track := range o.tracks { 253 | track.Stop() 254 | track.DeleteOutgoingSourceGroup(o.transport) 255 | } 256 | 257 | o.tracks = make(map[string]*OutgoingStreamTrack, 0) 258 | 259 | o.transport = nil 260 | } 261 | -------------------------------------------------------------------------------- /transport_test.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | 8 | "github.com/notedit/sdp" 9 | ) 10 | 11 | const sdpStr = "v=1\r\n" + 12 | "o=- 4327261771880257373 2 IN IP4 127.0.0.1\r\n" + 13 | "s=-\r\n" + 14 | "t=1 1\r\n" + 15 | "a=group:BUNDLE audio video\r\n" + 16 | "a=msid-semantic: WMS xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj\r\n" + 17 | "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" + 18 | "c=IN IP4 0.0.0.0\r\n" + 19 | "a=rtcp:9 IN IP4 0.0.0.0\r\n" + 20 | "a=ice-ufrag:ez5G\r\n" + 21 | "a=ice-pwd:1F1qS++jzWLSQi0qQDZkX/QV\r\n" + 22 | "a=candidate:1 1 UDP 33554431 35.188.215.104 59110 typ host\r\n" + 23 | "a=fingerprint:sha-256 D2:FA:0E:C3:22:59:5E:14:95:69:92:3D:13:B4:84:24:2C:C2:A2:C0:3E:FD:34:8E:5E:EA:6F:AF:52:CE:E6:0F\r\n" + 24 | "a=setup:actpass\r\n" + 25 | "a=connection:new\r\n" + 26 | "a=mid:audio\r\n" + 27 | "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + 28 | "a=sendrecv\r\n" + 29 | "a=rtcp-mux\r\n" + 30 | "a=rtpmap:111 opus/48000/2\r\n" + 31 | "a=rtcp-fb:111 transport-cc\r\n" + 32 | "a=fmtp:111 minptime=10;useinbandfec=1\r\n" + 33 | "a=rtpmap:103 ISAC/16000\r\n" + 34 | "a=rtpmap:104 ISAC/32000\r\n" + 35 | "a=rtpmap:9 G722/8000\r\n" + 36 | "a=rtpmap:0 PCMU/8000\r\n" + 37 | "a=rtpmap:8 PCMA/8000\r\n" + 38 | "a=rtpmap:106 CN/32000\r\n" + 39 | "a=rtpmap:105 CN/16000\r\n" + 40 | "a=rtpmap:13 CN/8000\r\n" + 41 | "a=rtpmap:110 telephone-event/48000\r\n" + 42 | "a=rtpmap:112 telephone-event/32000\r\n" + 43 | "a=rtpmap:113 telephone-event/16000\r\n" + 44 | "a=rtpmap:126 telephone-event/8000\r\n" + 45 | "a=ssrc:3510681183 cname:loqPWNg7JMmrFUnr\r\n" + 46 | "a=ssrc:3510681183 msid:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj 7ea47500-22eb-4815-a899-c74ef321b6ee\r\n" + 47 | "a=ssrc:3510681183 mslabel:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj\r\n" + 48 | "a=ssrc:3510681183 label:7ea47500-22eb-4815-a899-c74ef321b6ee\r\n" + 49 | "m=video 9 UDP/TLS/RTP/SAVPF 96 98 100 102 127 125 97 99 101 124\r\n" + 50 | "c=IN IP4 0.0.0.0\r\n" + 51 | "a=connection:new\r\n" + 52 | "a=rtcp:9 IN IP4 0.0.0.0\r\n" + 53 | "a=ice-ufrag:ez5G\r\n" + 54 | "a=ice-pwd:1F1qS++jzWLSQi0qQDZkX/QV\r\n" + 55 | "a=candidate:1 1 UDP 33554431 35.188.215.104 59110 typ host\r\n" + 56 | "a=fingerprint:sha-256 D2:FA:0E:C3:22:59:5E:14:95:69:92:3D:13:B4:84:24:2C:C2:A2:C0:3E:FD:34:8E:5E:EA:6F:AF:52:CE:E6:0F\r\n" + 57 | "a=setup:actpass\r\n" + 58 | "a=mid:video\r\n" + 59 | "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n" + 60 | "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" + 61 | "a=extmap:4 urn:3gpp:video-orientation\r\n" + 62 | "a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n" + 63 | "a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n" + 64 | "a=sendrecv\r\n" + 65 | "a=rtcp-mux\r\n" + 66 | "a=rtcp-rsize\r\n" + 67 | "a=rtpmap:96 VP8/90000\r\n" + 68 | "a=rtcp-fb:96 ccm fir\r\n" + 69 | "a=rtcp-fb:96 nack\r\n" + 70 | "a=rtcp-fb:96 nack pli\r\n" + 71 | "a=rtcp-fb:96 goog-remb\r\n" + 72 | "a=rtcp-fb:96 transport-cc\r\n" + 73 | "a=rtpmap:98 VP9/90000\r\n" + 74 | "a=rtcp-fb:98 ccm fir\r\n" + 75 | "a=rtcp-fb:98 nack\r\n" + 76 | "a=rtcp-fb:98 nack pli\r\n" + 77 | "a=rtcp-fb:98 goog-remb\r\n" + 78 | "a=rtcp-fb:98 transport-cc\r\n" + 79 | "a=rtpmap:100 H264/90000\r\n" + 80 | "a=rtcp-fb:100 ccm fir\r\n" + 81 | "a=rtcp-fb:100 nack\r\n" + 82 | "a=rtcp-fb:100 nack pli\r\n" + 83 | "a=rtcp-fb:100 goog-remb\r\n" + 84 | "a=rtcp-fb:100 transport-cc\r\n" + 85 | "a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n" + 86 | "a=rtpmap:102 red/90000\r\n" + 87 | "a=rtpmap:127 ulpfec/90000\r\n" + 88 | "a=rtpmap:125 flexfec-03/90000\r\n" + 89 | "a=rtcp-fb:125 ccm fir\r\n" + 90 | "a=rtcp-fb:125 nack\r\n" + 91 | "a=rtcp-fb:125 nack pli\r\n" + 92 | "a=rtcp-fb:125 goog-remb\r\n" + 93 | "a=rtcp-fb:125 transport-cc\r\n" + 94 | "a=fmtp:125 repair-window=10000000\r\n" + 95 | "a=rtpmap:97 rtx/90000\r\n" + 96 | "a=fmtp:97 apt=96\r\n" + 97 | "a=rtpmap:99 rtx/90000\r\n" + 98 | "a=fmtp:99 apt=98\r\n" + 99 | "a=rtpmap:101 rtx/90000\r\n" + 100 | "a=fmtp:101 apt=100\r\n" + 101 | "a=rtpmap:124 rtx/90000\r\n" + 102 | "a=fmtp:124 apt=102\r\n" + 103 | "a=ssrc-group:FID 3004364195 1126032854\r\n" + 104 | "a=ssrc-group:FEC-FR 3004364195 1080772241\r\n" + 105 | "a=ssrc:3004364195 cname:loqPWNg7JMmrFUnr\r\n" + 106 | "a=ssrc:3004364195 msid:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj cf093ab0-0b28-4930-8fe1-7ca8d529be25\r\n" + 107 | "a=ssrc:3004364195 mslabel:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj\r\n" + 108 | "a=ssrc:3004364195 label:cf093ab0-0b28-4930-8fe1-7ca8d529be25\r\n" + 109 | "a=ssrc:1126032854 cname:loqPWNg7JMmrFUnr\r\n" + 110 | "a=ssrc:1126032854 msid:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj cf093ab0-0b28-4930-8fe1-7ca8d529be25\r\n" + 111 | "a=ssrc:1126032854 mslabel:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj\r\n" + 112 | "a=ssrc:1126032854 label:cf093ab0-0b28-4930-8fe1-7ca8d529be25\r\n" + 113 | "a=ssrc:1080772241 cname:loqPWNg7JMmrFUnr\r\n" + 114 | "a=ssrc:1080772241 msid:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj cf093ab0-0b28-4930-8fe1-7ca8d529be25\r\n" + 115 | "a=ssrc:1080772241 mslabel:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj\r\n" + 116 | "a=ssrc:1080772241 label:cf093ab0-0b28-4930-8fe1-7ca8d529be25\r\n" 117 | 118 | func Test_TransportCreate(t *testing.T) { 119 | 120 | EnableLog(true) 121 | endpoint := NewEndpoint("127.0.0.1") 122 | 123 | iceInfo := sdp.GenerateICEInfo(true) 124 | dtlsInfo := sdp.NewDTLSInfo(sdp.SETUPACTPASS, "sha-256", "F2:AA:0E:C3:22:59:5E:14:95:69:92:3D:13:B4:84:24:2C:C2:A2:C0:3E:FD:34:8E:5E:EA:6F:AF:52:CE:E6:0F") 125 | sdpInfo := sdp.NewSDPInfo() 126 | sdpInfo.SetICE(iceInfo) 127 | sdpInfo.SetDTLS(dtlsInfo) 128 | transport := endpoint.CreateTransport(sdpInfo, nil) 129 | 130 | if transport == nil { 131 | t.Error("can not create transport") 132 | } 133 | t.Log("yes") 134 | } 135 | 136 | func Test_CreateIncomingTrack(t *testing.T) { 137 | 138 | endpoint := NewEndpoint("127.0.0.1") 139 | iceInfo := sdp.ICEInfoGenerate(true) 140 | dtlsInfo := sdp.NewDTLSInfo(sdp.SETUPACTPASS, "sha-256", "F2:AA:0E:C3:22:59:5E:14:95:69:92:3D:13:B4:84:24:2C:C2:A2:C0:3E:FD:34:8E:5E:EA:6F:AF:52:CE:E6:0F") 141 | sdpInfo := sdp.NewSDPInfo() 142 | sdpInfo.SetICE(iceInfo) 143 | sdpInfo.SetDTLS(dtlsInfo) 144 | 145 | transport := endpoint.CreateTransport(sdpInfo, nil) 146 | 147 | incomingTrack := transport.CreateIncomingStreamTrack("audio", "audiotrack", map[string]uint{}) 148 | 149 | if incomingTrack.GetID() != "audiotrack" { 150 | t.Error("create incoming track error") 151 | } 152 | t.Log("yes") 153 | } 154 | 155 | func Test_CreateOutgoingTrack(t *testing.T) { 156 | 157 | endpoint := NewEndpoint("127.0.0.1") 158 | iceInfo := sdp.ICEInfoGenerate(true) 159 | dtlsInfo := sdp.NewDTLSInfo(sdp.SETUPACTPASS, "sha-256", "F2:AA:0E:C3:22:59:5E:14:95:69:92:3D:13:B4:84:24:2C:C2:A2:C0:3E:FD:34:8E:5E:EA:6F:AF:52:CE:E6:0F") 160 | sdpInfo := sdp.NewSDPInfo() 161 | sdpInfo.SetICE(iceInfo) 162 | sdpInfo.SetDTLS(dtlsInfo) 163 | 164 | transport := endpoint.CreateTransport(sdpInfo, nil) 165 | outgoingTrack := transport.CreateOutgoingStreamTrack("video", "videotrack", map[string]uint{}) 166 | 167 | if outgoingTrack.GetID() != "videotrack" { 168 | t.Error("create outgoing track error") 169 | } 170 | } 171 | 172 | func Test_TransportStop(t *testing.T) { 173 | 174 | EnableLog(false) 175 | endpoint := NewEndpoint("127.0.0.1") 176 | 177 | iceInfo := sdp.ICEInfoGenerate(true) 178 | dtlsInfo := sdp.NewDTLSInfo(sdp.SETUPACTPASS, "sha-256", "F2:AA:0E:C3:22:59:5E:14:95:69:92:3D:13:B4:84:24:2C:C2:A2:C0:3E:FD:34:8E:5E:EA:6F:AF:52:CE:E6:0F") 179 | sdpInfo := sdp.NewSDPInfo() 180 | sdpInfo.SetICE(iceInfo) 181 | sdpInfo.SetDTLS(dtlsInfo) 182 | 183 | transport := endpoint.CreateTransport(sdpInfo, nil) 184 | 185 | transport.Stop() 186 | } 187 | 188 | func Test_TransportCreateStream(t *testing.T) { 189 | 190 | EnableLog(false) 191 | 192 | endpoint := NewEndpoint("127.0.0.1") 193 | 194 | offer, error := sdp.Parse(string(sdpStr)) 195 | 196 | if error != nil { 197 | log.Printf("%s", error) 198 | return 199 | } 200 | 201 | transport := endpoint.CreateTransport(offer, nil) 202 | 203 | transport.SetRemoteProperties(offer.GetMedia("audio"), offer.GetMedia("video")) 204 | 205 | streamInfo := offer.GetFirstStream() 206 | 207 | incoming := transport.CreateIncomingStream(streamInfo) 208 | 209 | fmt.Println(incoming) 210 | 211 | transport.Stop() 212 | 213 | } 214 | -------------------------------------------------------------------------------- /incomingstream.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | "sync" 9 | 10 | native "github.com/notedit/media-server-go/wrapper" 11 | "github.com/notedit/sdp" 12 | ) 13 | 14 | // IncomingStream The incoming streams represent the recived media stream from a remote peer. 15 | type IncomingStream struct { 16 | id string 17 | info *sdp.StreamInfo 18 | transport native.DTLSICETransport 19 | receiver native.RTPReceiverFacade 20 | tracks map[string]*IncomingStreamTrack 21 | onStreamAddIncomingTrackListeners []func(*IncomingStreamTrack) 22 | l sync.Mutex 23 | } 24 | 25 | 26 | // NewIncomingStream Create new incoming stream 27 | // TODO: make this public 28 | func newIncomingStream(transport native.DTLSICETransport, receiver native.RTPReceiverFacade, info *sdp.StreamInfo) *IncomingStream { 29 | stream := &IncomingStream{} 30 | stream.id = info.GetID() 31 | stream.transport = transport 32 | stream.receiver = receiver 33 | stream.tracks = make(map[string]*IncomingStreamTrack) 34 | 35 | stream.onStreamAddIncomingTrackListeners = make([]func(*IncomingStreamTrack), 0) 36 | 37 | for _, track := range info.GetTracks() { 38 | stream.CreateTrack(track) 39 | } 40 | return stream 41 | } 42 | 43 | 44 | // GetID get id 45 | func (i *IncomingStream) GetID() string { 46 | return i.id 47 | } 48 | 49 | // GetStreamInfo get stream info 50 | func (i *IncomingStream) GetStreamInfo() *sdp.StreamInfo { 51 | 52 | info := sdp.NewStreamInfo(i.id) 53 | 54 | for _, track := range i.tracks { 55 | info.AddTrack(track.GetTrackInfo().Clone()) 56 | } 57 | return info 58 | } 59 | 60 | // GetStats Get statistics for all tracks in the stream 61 | func (i *IncomingStream) GetStats() map[string]map[string]*IncomingAllStats { 62 | 63 | stats := map[string]map[string]*IncomingAllStats{} 64 | 65 | for _, track := range i.tracks { 66 | stats[track.GetID()] = track.GetStats() 67 | } 68 | 69 | return stats 70 | } 71 | 72 | // GetTrack Get track by id 73 | func (i *IncomingStream) GetTrack(trackID string) *IncomingStreamTrack { 74 | i.l.Lock() 75 | defer i.l.Unlock() 76 | return i.tracks[trackID] 77 | } 78 | 79 | // GetTracks Get all tracks in this stream 80 | func (i *IncomingStream) GetTracks() []*IncomingStreamTrack { 81 | i.l.Lock() 82 | defer i.l.Unlock() 83 | tracks := []*IncomingStreamTrack{} 84 | for _, track := range i.tracks { 85 | tracks = append(tracks, track) 86 | } 87 | return tracks 88 | } 89 | 90 | // GetAudioTracks get all audio tracks 91 | func (i *IncomingStream) GetAudioTracks() []*IncomingStreamTrack { 92 | i.l.Lock() 93 | defer i.l.Unlock() 94 | audioTracks := []*IncomingStreamTrack{} 95 | for _, track := range i.tracks { 96 | if strings.ToLower(track.GetMedia()) == "audio" { 97 | audioTracks = append(audioTracks, track) 98 | } 99 | } 100 | return audioTracks 101 | } 102 | 103 | // GetVideoTracks get all video tracks 104 | func (i *IncomingStream) GetVideoTracks() []*IncomingStreamTrack { 105 | i.l.Lock() 106 | defer i.l.Unlock() 107 | videoTracks := []*IncomingStreamTrack{} 108 | for _, track := range i.tracks { 109 | if strings.ToLower(track.GetMedia()) == "video" { 110 | videoTracks = append(videoTracks, track) 111 | } 112 | } 113 | return videoTracks 114 | } 115 | 116 | // AddTrack Adds an incoming stream track created using the Transpocnder.CreateIncomingStreamTrack to this stream 117 | func (i *IncomingStream) AddTrack(track *IncomingStreamTrack) error { 118 | 119 | i.l.Lock() 120 | defer i.l.Unlock() 121 | if _, ok := i.tracks[track.GetID()]; ok { 122 | return errors.New("Track id already present in stream") 123 | } 124 | 125 | i.tracks[track.GetID()] = track 126 | return nil 127 | } 128 | 129 | func (i *IncomingStream) RemoveTrack(track *IncomingStreamTrack) error { 130 | 131 | i.l.Lock() 132 | defer i.l.Unlock() 133 | 134 | delete(i.tracks,track.GetID()) 135 | return nil 136 | } 137 | 138 | // CreateTrack Create new track from a TrackInfo object and add it to this stream 139 | func (i *IncomingStream) CreateTrack(track *sdp.TrackInfo) *IncomingStreamTrack { 140 | 141 | if _, ok := i.tracks[track.GetID()]; ok { 142 | return nil 143 | } 144 | 145 | var mediaType native.MediaFrameType = 0 146 | if track.GetMedia() == "video" { 147 | mediaType = 1 148 | } 149 | 150 | sources := map[string]native.RTPIncomingSourceGroup{} 151 | 152 | encodings := track.GetEncodings() 153 | 154 | if len(encodings) > 0 { 155 | 156 | for _, items := range encodings { 157 | 158 | for _, encoding := range items { 159 | 160 | source := native.NewRTPIncomingSourceGroup(mediaType, i.transport.GetTimeService()) 161 | 162 | mid := track.GetMediaID() 163 | 164 | rid := encoding.GetID() 165 | 166 | source.SetRid(rid) 167 | 168 | if mid != "" { 169 | source.SetMid(mid) 170 | } 171 | 172 | params := encoding.GetParams() 173 | 174 | if ssrc, ok := params["ssrc"]; ok { 175 | ssrcUint, err := strconv.ParseUint(ssrc, 10, 32) 176 | if err != nil { 177 | fmt.Println("ssrc parse error ", err) 178 | continue 179 | } 180 | source.GetMedia().SetSsrc(uint(ssrcUint)) 181 | groups := track.GetSourceGroupS() 182 | for _, group := range groups { 183 | // check if it is from us 184 | if group.GetSSRCs() != nil && group.GetSSRCs()[0] == source.GetMedia().GetSsrc() { 185 | if group.GetSemantics() == "FID" { 186 | source.GetRtx().SetSsrc(group.GetSSRCs()[1]) 187 | } 188 | 189 | if group.GetSemantics() == "FEC-FR" { 190 | source.GetFec().SetSsrc(group.GetSSRCs()[1]) 191 | } 192 | } 193 | } 194 | } 195 | 196 | i.transport.AddIncomingSourceGroup(source) 197 | sources[rid] = source 198 | 199 | // runtime.SetFinalizer(source, func(source native.RTPIncomingSourceGroup) { 200 | // i.transport.RemoveIncomingSourceGroup(source) 201 | // }) 202 | 203 | } 204 | } 205 | 206 | } else if track.GetSourceGroup("SIM") != nil { 207 | // chrome like simulcast 208 | SIM := track.GetSourceGroup("SIM") 209 | 210 | ssrcs := SIM.GetSSRCs() 211 | 212 | groups := track.GetSourceGroupS() 213 | 214 | for j, ssrc := range ssrcs { 215 | 216 | source := native.NewRTPIncomingSourceGroup(mediaType, i.transport.GetTimeService()) 217 | 218 | source.GetMedia().SetSsrc(ssrc) 219 | 220 | for _, group := range groups { 221 | 222 | if group.GetSSRCs()[0] == ssrc { 223 | 224 | if group.GetSemantics() == "FID" { 225 | source.GetRtx().SetSsrc(group.GetSSRCs()[1]) 226 | } 227 | 228 | if group.GetSemantics() == "FEC-FR" { 229 | source.GetFec().SetSsrc(group.GetSSRCs()[1]) 230 | } 231 | } 232 | } 233 | 234 | i.transport.AddIncomingSourceGroup(source) 235 | 236 | sources[strconv.Itoa(j)] = source 237 | 238 | // runtime.SetFinalizer(source, func(source native.RTPIncomingSourceGroup) { 239 | // i.transport.RemoveIncomingSourceGroup(source) 240 | // }) 241 | } 242 | 243 | } else { 244 | source := native.NewRTPIncomingSourceGroup(mediaType, i.transport.GetTimeService()) 245 | 246 | source.GetMedia().SetSsrc(track.GetSSRCS()[0]) 247 | 248 | fid := track.GetSourceGroup("FID") 249 | fec_fr := track.GetSourceGroup("FEC-FR") 250 | 251 | if fid != nil { 252 | source.GetRtx().SetSsrc(fid.GetSSRCs()[1]) 253 | } else { 254 | source.GetRtx().SetSsrc(0) 255 | } 256 | 257 | if fec_fr != nil { 258 | source.GetFec().SetSsrc(fec_fr.GetSSRCs()[1]) 259 | } else { 260 | source.GetFec().SetSsrc(0) 261 | } 262 | 263 | i.transport.AddIncomingSourceGroup(source) 264 | 265 | // Append to soruces with empty rid 266 | sources[""] = source 267 | 268 | } 269 | 270 | incomingTrack := NewIncomingStreamTrack(track.GetMedia(), track.GetID(), i.receiver, sources) 271 | 272 | i.l.Lock() 273 | i.tracks[track.GetID()] = incomingTrack 274 | i.l.Unlock() 275 | 276 | return incomingTrack 277 | } 278 | 279 | // Stop Removes the media strem from the transport and also detaches from any attached incoming stream 280 | func (i *IncomingStream) Stop() { 281 | 282 | if i.transport == nil { 283 | return 284 | } 285 | 286 | i.l.Lock() 287 | defer i.l.Unlock() 288 | 289 | for k, track := range i.tracks { 290 | track.Stop() 291 | delete(i.tracks, k) 292 | } 293 | 294 | 295 | native.DeleteRTPReceiverFacade(i.receiver) // other module maybe need delete 296 | i.receiver = nil 297 | i.transport = nil 298 | } 299 | -------------------------------------------------------------------------------- /transponder.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "sort" 7 | 8 | native "github.com/notedit/media-server-go/wrapper" 9 | ) 10 | 11 | type BitrateTraversal string 12 | 13 | const ( 14 | TraversalDefault BitrateTraversal = "default" 15 | TraversalSpatialTemporal BitrateTraversal = "spatial-temporal" 16 | TraversalTemporalSpatial BitrateTraversal = "temporal-spatial" 17 | TraversalZigZagSpatialTemporal BitrateTraversal = "zig-zag-spatial-temporal" 18 | TraversalZigZagTemporalSpatial BitrateTraversal = "zig-zag-temporal-spatial" 19 | ) 20 | 21 | // Transponder 22 | type Transponder struct { 23 | muted bool 24 | track *IncomingStreamTrack 25 | transponder native.RTPStreamTransponderFacade 26 | encodingId string 27 | spatialLayerId int 28 | temporalLayerId int 29 | maxSpatialLayerId int 30 | maxTemporalLayerId int 31 | onStopListeners []func() 32 | } 33 | 34 | func NewTransponder(transponderFacade native.RTPStreamTransponderFacade) *Transponder { 35 | transponder := new(Transponder) 36 | transponder.muted = false 37 | 38 | transponder.transponder = transponderFacade 39 | transponder.spatialLayerId = MaxLayerId 40 | transponder.temporalLayerId = MaxLayerId 41 | transponder.maxSpatialLayerId = MaxLayerId 42 | transponder.maxTemporalLayerId = MaxLayerId 43 | 44 | transponder.onStopListeners = make([]func(), 0) 45 | 46 | return transponder 47 | } 48 | 49 | func (t *Transponder) SetIncomingTrack(incomingTrack *IncomingStreamTrack) error { 50 | 51 | if t.transponder == nil { 52 | return errors.New("Transponder is already closed") 53 | } 54 | 55 | if incomingTrack == nil { 56 | return errors.New("Track can not be nil") 57 | } 58 | 59 | if t.track != nil { 60 | t.track.Detached() 61 | } 62 | 63 | t.track = incomingTrack 64 | 65 | // we need make sure first encoding id not nil 66 | // todo check 67 | // get first encoding 68 | encoding := t.track.GetFirstEncoding() 69 | if encoding == nil { 70 | panic("encoding is nil") 71 | } 72 | 73 | t.transponder.SetIncoming(encoding.GetSource(), incomingTrack.receiver) 74 | 75 | t.encodingId = encoding.GetID() 76 | 77 | t.spatialLayerId = MaxLayerId 78 | t.temporalLayerId = MaxLayerId 79 | t.maxSpatialLayerId = MaxLayerId 80 | t.maxTemporalLayerId = MaxLayerId 81 | 82 | t.track.Attached() 83 | 84 | return nil 85 | } 86 | 87 | func (t *Transponder) GetIncomingTrack() *IncomingStreamTrack { 88 | return t.track 89 | } 90 | 91 | // GetAvailableLayers Get available encodings and layers 92 | func (t *Transponder) GetAvailableLayers() *ActiveLayersInfo { 93 | if t.track != nil { 94 | return t.track.GetActiveLayers() 95 | } 96 | return nil 97 | } 98 | 99 | func (t *Transponder) IsMuted() bool { 100 | return t.muted 101 | } 102 | 103 | func (t *Transponder) Mute(muting bool) { 104 | 105 | if t.muted != muting { 106 | t.muted = muting 107 | if t.transponder != nil { 108 | t.transponder.Mute(muting) 109 | } 110 | } 111 | } 112 | 113 | func getSpatialLayerId(layer *Layer) int { 114 | if layer.SpatialLayerId != MaxLayerId { 115 | return layer.SpatialLayerId 116 | } 117 | return layer.SimulcastIdx 118 | } 119 | 120 | func (t *Transponder) SetTargetBitrate(bitrate uint, traversal BitrateTraversal, strict bool) uint { 121 | 122 | if t.track == nil { 123 | return 0 124 | } 125 | 126 | var current uint 127 | var encodingId string 128 | var encodingIdMin string 129 | 130 | spatialLayerId := MaxLayerId 131 | temporalLayerId := MaxLayerId 132 | 133 | min := uint(math.MaxInt32) 134 | 135 | spatialLayerIdMin := MaxLayerId 136 | temporalLayerIdMin := MaxLayerId 137 | 138 | var orderfunc func(i int, j int) bool 139 | layers := t.track.GetActiveLayers().Layers 140 | 141 | if len(layers) == 0 { 142 | t.Mute(false) 143 | return 0 144 | } 145 | 146 | switch traversal { 147 | case TraversalSpatialTemporal: 148 | orderfunc = func(i, j int) bool { 149 | a := layers[i] 150 | b := layers[j] 151 | 152 | ret1 := getSpatialLayerId(b)*MaxLayerId + b.TemporalLayerId 153 | ret2 := getSpatialLayerId(a)*MaxLayerId + a.TemporalLayerId 154 | if ret1-ret2 < 0 { 155 | return true 156 | } 157 | return false 158 | } 159 | break 160 | case TraversalZigZagSpatialTemporal: 161 | orderfunc = func(i, j int) bool { 162 | a := layers[i] 163 | b := layers[j] 164 | 165 | ret1 := (getSpatialLayerId(b)+b.TemporalLayerId+1)*MaxLayerId - b.TemporalLayerId 166 | ret2 := (getSpatialLayerId(a)+a.TemporalLayerId+1)*MaxLayerId - a.TemporalLayerId 167 | if ret1-ret2 < 0 { 168 | return true 169 | } 170 | return false 171 | } 172 | break 173 | case TraversalTemporalSpatial: 174 | orderfunc = func(i, j int) bool { 175 | a := layers[i] 176 | b := layers[j] 177 | 178 | ret1 := b.TemporalLayerId*MaxLayerId + getSpatialLayerId(b) 179 | ret2 := a.TemporalLayerId*MaxLayerId + getSpatialLayerId(a) 180 | if ret1-ret2 < 0 { 181 | return true 182 | } 183 | return false 184 | } 185 | break 186 | case TraversalZigZagTemporalSpatial: 187 | orderfunc = func(i, j int) bool { 188 | a := layers[i] 189 | b := layers[j] 190 | 191 | ret1 := (getSpatialLayerId(b)+b.TemporalLayerId+1)*MaxLayerId - getSpatialLayerId(b) 192 | ret2 := (getSpatialLayerId(a)+a.TemporalLayerId+1)*MaxLayerId - getSpatialLayerId(a) 193 | if ret1-ret2 < 0 { 194 | return true 195 | } 196 | return false 197 | } 198 | break 199 | default: 200 | break 201 | } 202 | 203 | if orderfunc != nil { 204 | sort.SliceStable(layers, orderfunc) 205 | } 206 | 207 | for _, layer := range layers { 208 | 209 | if layer.Bitrate <= bitrate && layer.Bitrate > current && t.maxSpatialLayerId >= layer.SpatialLayerId && t.maxSpatialLayerId >= layer.TemporalLayerId { 210 | encodingId = layer.EncodingId 211 | spatialLayerId = layer.SpatialLayerId 212 | temporalLayerId = layer.TemporalLayerId 213 | current = layer.Bitrate 214 | break 215 | } 216 | 217 | if layer.Bitrate > 0 && layer.Bitrate < min && t.maxSpatialLayerId >= layer.SpatialLayerId { 218 | encodingIdMin = layer.EncodingId 219 | spatialLayerIdMin = layer.SpatialLayerId 220 | temporalLayerIdMin = layer.TemporalLayerId 221 | min = layer.Bitrate 222 | } 223 | } 224 | 225 | // Check if we have been able to find a layer that matched the target bitrate 226 | if current <= 0 { 227 | 228 | if strict == false { 229 | t.Mute(false) 230 | t.SelectEncoding(encodingIdMin) 231 | t.SelectLayer(spatialLayerIdMin, temporalLayerIdMin) 232 | return min 233 | } else { 234 | t.Mute(true) 235 | return 0 236 | } 237 | } 238 | t.Mute(false) 239 | t.SelectEncoding(encodingId) 240 | t.SelectLayer(spatialLayerId, temporalLayerId) 241 | return current 242 | } 243 | 244 | // SelectEncoding by id 245 | func (t *Transponder) SelectEncoding(encodingId string) { 246 | 247 | if t.encodingId == encodingId { 248 | return 249 | } 250 | encoding := t.track.GetEncoding(encodingId) 251 | if encoding == nil { 252 | return 253 | } 254 | t.transponder.SetIncoming(encoding.GetSource(), t.track.receiver) 255 | t.encodingId = encodingId 256 | } 257 | 258 | // GetSelectedEncoding get selected encoding id 259 | func (t *Transponder) GetSelectedEncoding() string { 260 | return t.encodingId 261 | } 262 | 263 | // GetSelectedSpatialLayerId return int 264 | func (t *Transponder) GetSelectedSpatialLayerId() int { 265 | return t.spatialLayerId 266 | } 267 | 268 | // GetSelectedTemporalLayerId return int 269 | func (t *Transponder) GetSelectedTemporalLayerId() int { 270 | return t.temporalLayerId 271 | } 272 | 273 | // SelectLayer Select SVC temporatl and spatial layers. Only available for VP9 media. 274 | func (t *Transponder) SelectLayer(spatialLayerId, temporalLayerId int) { 275 | 276 | spatialLayerId = Min(spatialLayerId, t.maxSpatialLayerId) 277 | temporalLayerId = Min(temporalLayerId, t.maxTemporalLayerId) 278 | 279 | if t.spatialLayerId == spatialLayerId && t.temporalLayerId == temporalLayerId { 280 | return 281 | } 282 | 283 | t.transponder.SelectLayer(spatialLayerId, temporalLayerId) 284 | 285 | t.spatialLayerId = spatialLayerId 286 | t.temporalLayerId = temporalLayerId 287 | } 288 | 289 | func (t *Transponder) SetMaximumLayers(maxSpatialLayerId, maxTemporalLayerId int) { 290 | 291 | if maxSpatialLayerId < 0 || maxTemporalLayerId < 0 { 292 | return 293 | } 294 | 295 | t.maxSpatialLayerId = maxSpatialLayerId 296 | t.maxTemporalLayerId = maxTemporalLayerId 297 | } 298 | 299 | 300 | // Stop stop this transponder 301 | func (t *Transponder) Stop() { 302 | 303 | if t.transponder == nil { 304 | return 305 | } 306 | 307 | if t.track != nil { 308 | t.track.Detached() 309 | } 310 | 311 | t.transponder.Close() 312 | 313 | native.DeleteRTPStreamTransponderFacade(t.transponder) 314 | 315 | t.transponder = nil 316 | 317 | t.track = nil 318 | } 319 | -------------------------------------------------------------------------------- /incomingstreamtrack.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "time" 7 | 8 | native "github.com/notedit/media-server-go/wrapper" 9 | "github.com/notedit/sdp" 10 | ) 11 | 12 | // Layer info 13 | type Layer struct { 14 | // EncodingId str 15 | EncodingId string 16 | // SpatialLayerId int 17 | SpatialLayerId int 18 | // TemporalLayerId int 19 | TemporalLayerId int 20 | // SimulcastIdx int 21 | SimulcastIdx int 22 | // TotalBytes uint 23 | TotalBytes uint 24 | // NumPackets uint 25 | NumPackets uint 26 | // Bitrate uint 27 | Bitrate uint 28 | } 29 | 30 | // Encoding info 31 | type Encoding struct { 32 | id string 33 | source native.RTPIncomingSourceGroup 34 | depacketizer native.StreamTrackDepacketizer 35 | } 36 | 37 | // GetID encoding id 38 | func (e *Encoding) GetID() string { 39 | return e.id 40 | } 41 | 42 | // GetSource get native RTPIncomingSourceGroup 43 | func (e *Encoding) GetSource() native.RTPIncomingSourceGroup { 44 | return e.source 45 | } 46 | 47 | // GetDepacketizer get native StreamTrackDepacketizer 48 | func (e *Encoding) GetDepacketizer() native.StreamTrackDepacketizer { 49 | return e.depacketizer 50 | } 51 | 52 | // IncomingTrackStopListener stop listener 53 | type IncomingTrackStopListener func() 54 | 55 | // IncomingStreamTrack Audio or Video track of a remote media stream 56 | type IncomingStreamTrack struct { 57 | id string 58 | media string 59 | receiver native.RTPReceiverFacade 60 | counter int 61 | encodings []*Encoding 62 | trackInfo *sdp.TrackInfo 63 | stats map[string]*IncomingAllStats 64 | mediaframeMultiplexer *MediaFrameMultiplexer 65 | onStopListeners []func() 66 | onAttachedListeners []func() 67 | onDetachedListeners []func() 68 | } 69 | 70 | // IncomingStats info 71 | type IncomingStats struct { 72 | LostPackets uint 73 | DropPackets uint 74 | NumPackets uint 75 | NumRTCPPackets uint 76 | TotalBytes uint 77 | TotalRTCPBytes uint 78 | TotalPLIs uint 79 | TotalNACKs uint 80 | Bitrate uint 81 | Layers []*Layer 82 | } 83 | 84 | // IncomingAllStats info 85 | type IncomingAllStats struct { 86 | Rtt uint 87 | MinWaitTime uint 88 | MaxWaitTime uint 89 | AvgWaitTime float64 90 | Media *IncomingStats 91 | Rtx *IncomingStats 92 | Fec *IncomingStats 93 | Bitrate uint 94 | Total uint 95 | Remb uint 96 | SimulcastIdx int 97 | timestamp int64 98 | } 99 | 100 | // ActiveEncoding info 101 | type ActiveEncoding struct { 102 | EncodingId string 103 | SimulcastIdx int 104 | Bitrate uint 105 | Layers []*Layer 106 | } 107 | 108 | // ActiveLayersInfo info 109 | type ActiveLayersInfo struct { 110 | Active []*ActiveEncoding 111 | Inactive []*ActiveEncoding 112 | Layers []*Layer 113 | } 114 | 115 | func getStatsFromIncomingSource(source native.RTPIncomingSource) *IncomingStats { 116 | 117 | stats := &IncomingStats{ 118 | LostPackets: source.GetLostPackets(), 119 | DropPackets: source.GetDropPackets(), 120 | NumPackets: source.GetNumPackets(), 121 | NumRTCPPackets: source.GetNumRTCPPackets(), 122 | TotalBytes: source.GetTotalBytes(), 123 | TotalRTCPBytes: source.GetTotalRTCPBytes(), 124 | TotalPLIs: source.GetTotalPLIs(), 125 | TotalNACKs: source.GetTotalNACKs(), 126 | Bitrate: source.GetBitrate(), 127 | Layers: []*Layer{}, 128 | } 129 | 130 | layers := source.Layers() 131 | 132 | individual := []*Layer{} 133 | 134 | var i int64 135 | for i = 0; i < layers.Size(); i++ { 136 | layer := layers.Get(int64(i)) 137 | 138 | layerInfo := &Layer{ 139 | SpatialLayerId: int(layer.GetSpatialLayerId()), 140 | TemporalLayerId: int(layer.GetTemporalLayerId()), 141 | TotalBytes: layer.GetTotalBytes(), 142 | NumPackets: layer.GetNumPackets(), 143 | Bitrate: layer.GetBitrate(), 144 | } 145 | 146 | individual = append(individual, layerInfo) 147 | } 148 | 149 | for _, layer := range individual { 150 | 151 | aggregated := &Layer{ 152 | SpatialLayerId: layer.SpatialLayerId, 153 | TemporalLayerId: layer.TemporalLayerId, 154 | TotalBytes: 0, 155 | NumPackets: 0, 156 | Bitrate: 0, 157 | } 158 | 159 | for _, layer2 := range individual { 160 | if layer2.SpatialLayerId <= aggregated.SpatialLayerId && layer2.TemporalLayerId <= aggregated.TemporalLayerId { 161 | aggregated.TotalBytes += layer2.TotalBytes 162 | aggregated.NumPackets += layer2.NumPackets 163 | aggregated.Bitrate += layer2.Bitrate 164 | } 165 | } 166 | 167 | stats.Layers = append(stats.Layers, aggregated) 168 | } 169 | 170 | return stats 171 | } 172 | 173 | // NewIncomingStreamTrack Create incoming audio/video track 174 | func NewIncomingStreamTrack(media string, id string, receiver native.RTPReceiverFacade, sources map[string]native.RTPIncomingSourceGroup) *IncomingStreamTrack { 175 | track := &IncomingStreamTrack{} 176 | 177 | track.id = id 178 | track.media = media 179 | track.receiver = receiver 180 | track.counter = 0 181 | track.encodings = make([]*Encoding, 0) 182 | 183 | track.trackInfo = sdp.NewTrackInfo(id, media) 184 | 185 | for k, source := range sources { 186 | encoding := &Encoding{ 187 | id: k, 188 | source: source, 189 | depacketizer: native.NewStreamTrackDepacketizer(source), 190 | } 191 | 192 | track.encodings = append(track.encodings, encoding) 193 | 194 | //Add ssrcs to track info 195 | if source.GetMedia().GetSsrc() > 0 { 196 | track.trackInfo.AddSSRC(source.GetMedia().GetSsrc()) 197 | } 198 | 199 | if source.GetRtx().GetSsrc() > 0 { 200 | track.trackInfo.AddSSRC(source.GetRtx().GetSsrc()) 201 | } 202 | 203 | if source.GetFec().GetSsrc() > 0 { 204 | track.trackInfo.AddSSRC(source.GetFec().GetSsrc()) 205 | } 206 | 207 | //Add RTX and FEC groups 208 | if source.GetRtx().GetSsrc() > 0 { 209 | sourceGroup := sdp.NewSourceGroupInfo("FID", []uint{source.GetMedia().GetSsrc(), source.GetRtx().GetSsrc()}) 210 | track.trackInfo.AddSourceGroup(sourceGroup) 211 | } 212 | 213 | if source.GetFec().GetSsrc() > 0 { 214 | sourceGroup := sdp.NewSourceGroupInfo("FEC-FR", []uint{source.GetMedia().GetSsrc(), source.GetFec().GetSsrc()}) 215 | track.trackInfo.AddSourceGroup(sourceGroup) 216 | } 217 | 218 | // if simulcast 219 | if len(k) > 0 { 220 | // make soure the pasused 221 | encodingInfo := sdp.NewTrackEncodingInfo(k, false) 222 | if source.GetMedia().GetSsrc() > 0 { 223 | ssrc := strconv.FormatUint(uint64(source.GetMedia().GetSsrc()), 10) 224 | encodingInfo.AddParam("ssrc", ssrc) 225 | } 226 | track.trackInfo.AddEncoding(encodingInfo) 227 | } 228 | } 229 | 230 | track.onAttachedListeners = make([]func(), 0) 231 | track.onDetachedListeners = make([]func(), 0) 232 | track.onStopListeners = make([]func(), 0) 233 | 234 | sort.SliceStable(track.encodings, func(i, j int) bool { 235 | return track.encodings[i].id < track.encodings[j].id 236 | }) 237 | 238 | return track 239 | } 240 | 241 | // GetID get track id 242 | func (i *IncomingStreamTrack) GetID() string { 243 | return i.id 244 | } 245 | 246 | // GetMedia get track media type "video" or "audio" 247 | func (i *IncomingStreamTrack) GetMedia() string { 248 | return i.media 249 | } 250 | 251 | // GetTrackInfo get track info 252 | func (i *IncomingStreamTrack) GetTrackInfo() *sdp.TrackInfo { 253 | return i.trackInfo 254 | } 255 | 256 | // GetSSRCs get all RTPIncomingSource include "media" "rtx" "fec" 257 | func (i *IncomingStreamTrack) GetSSRCs() []map[string]native.RTPIncomingSource { 258 | 259 | ssrcs := make([]map[string]native.RTPIncomingSource, 0) 260 | 261 | for _, encoding := range i.encodings { 262 | ssrcs = append(ssrcs, map[string]native.RTPIncomingSource{ 263 | "media": encoding.source.GetMedia(), 264 | "rtx": encoding.source.GetRtx(), 265 | "fec": encoding.source.GetFec(), 266 | }) 267 | } 268 | return ssrcs 269 | } 270 | 271 | // GetStats Get stats for all encodings 272 | func (i *IncomingStreamTrack) GetStats() map[string]*IncomingAllStats { 273 | 274 | if i.stats == nil { 275 | i.stats = map[string]*IncomingAllStats{} 276 | } 277 | 278 | for _, encoding := range i.encodings { 279 | state := i.stats[encoding.id] 280 | if state == nil || (state != nil && time.Now().UnixNano()-state.timestamp > 200000000) { 281 | 282 | encoding.GetSource().Update() 283 | 284 | media := getStatsFromIncomingSource(encoding.GetSource().GetMedia()) 285 | fec := getStatsFromIncomingSource(encoding.GetSource().GetFec()) 286 | rtx := getStatsFromIncomingSource(encoding.GetSource().GetRtx()) 287 | 288 | i.stats[encoding.id] = &IncomingAllStats{ 289 | Rtt: encoding.GetSource().GetRtt(), 290 | MinWaitTime: encoding.GetSource().GetMinWaitedTime(), 291 | MaxWaitTime: encoding.GetSource().GetMaxWaitedTime(), 292 | AvgWaitTime: encoding.GetSource().GetAvgWaitedTime(), 293 | Media: media, 294 | Rtx: rtx, 295 | Fec: fec, 296 | Bitrate: media.Bitrate, 297 | Total: media.Bitrate + fec.Bitrate + rtx.Bitrate, 298 | timestamp: time.Now().UnixNano(), 299 | } 300 | } 301 | } 302 | 303 | simulcastIdx := 0 304 | 305 | stats := []*IncomingAllStats{} 306 | 307 | for _, state := range i.stats { 308 | stats = append(stats, state) 309 | } 310 | 311 | sort.Slice(stats, func(i, j int) bool { return stats[i].Bitrate > stats[j].Bitrate }) 312 | 313 | for _, state := range stats { 314 | if state.Bitrate > 0 { 315 | simulcastIdx += 1 316 | state.SimulcastIdx = simulcastIdx 317 | } else { 318 | state.SimulcastIdx = -1 319 | } 320 | 321 | for _, layer := range state.Media.Layers { 322 | layer.SimulcastIdx = state.SimulcastIdx 323 | } 324 | } 325 | 326 | return i.stats 327 | } 328 | 329 | // GetActiveLayers Get active encodings and layers ordered by bitrate 330 | func (i *IncomingStreamTrack) GetActiveLayers() *ActiveLayersInfo { 331 | 332 | active := []*ActiveEncoding{} 333 | inactive := []*ActiveEncoding{} 334 | all := []*Layer{} 335 | 336 | stats := i.GetStats() 337 | 338 | for id, state := range stats { 339 | 340 | if state.Bitrate == 0 { 341 | inactive = append(inactive, &ActiveEncoding{ 342 | EncodingId: id, 343 | }) 344 | continue 345 | } 346 | 347 | encoding := &ActiveEncoding{ 348 | EncodingId: id, 349 | SimulcastIdx: state.SimulcastIdx, 350 | Bitrate: state.Bitrate, 351 | Layers: []*Layer{}, 352 | } 353 | 354 | layers := state.Media.Layers 355 | 356 | for _, layer := range layers { 357 | encoding.Layers = append(encoding.Layers, &Layer{ 358 | SimulcastIdx: layer.SimulcastIdx, 359 | SpatialLayerId: layer.SpatialLayerId, 360 | TemporalLayerId: layer.TemporalLayerId, 361 | Bitrate: layer.Bitrate, 362 | }) 363 | 364 | all = append(all, &Layer{ 365 | EncodingId: id, 366 | SimulcastIdx: layer.SimulcastIdx, 367 | SpatialLayerId: layer.SpatialLayerId, 368 | TemporalLayerId: layer.TemporalLayerId, 369 | Bitrate: layer.Bitrate, 370 | }) 371 | 372 | } 373 | 374 | if len(encoding.Layers) > 0 { 375 | sort.SliceStable(encoding.Layers, func(i, j int) bool { return encoding.Layers[i].Bitrate < encoding.Layers[j].Bitrate }) 376 | } else { 377 | 378 | all = append(all, &Layer{ 379 | EncodingId: encoding.EncodingId, 380 | SimulcastIdx: encoding.SimulcastIdx, 381 | SpatialLayerId: MaxLayerId, 382 | TemporalLayerId: MaxLayerId, 383 | Bitrate: encoding.Bitrate, 384 | }) 385 | } 386 | active = append(active, encoding) 387 | } 388 | 389 | if len(active) > 0 { 390 | sort.Slice(active, func(i, j int) bool { return active[i].Bitrate < active[j].Bitrate }) 391 | } 392 | 393 | if len(all) > 0 { 394 | sort.Slice(all, func(i, j int) bool { return all[i].Bitrate < all[j].Bitrate }) 395 | } 396 | 397 | return &ActiveLayersInfo{ 398 | Active: active, 399 | Inactive: inactive, 400 | Layers: all, 401 | } 402 | 403 | } 404 | 405 | // GetEncodings get all encodings 406 | func (i *IncomingStreamTrack) GetEncodings() []*Encoding { 407 | 408 | return i.encodings 409 | } 410 | 411 | // GetFirstEncoding get the first Encoding 412 | func (i *IncomingStreamTrack) GetFirstEncoding() *Encoding { 413 | 414 | for _, encoding := range i.encodings { 415 | if encoding != nil { 416 | return encoding 417 | } 418 | } 419 | return nil 420 | } 421 | 422 | // GetEncoding get Encoding by id 423 | func (i *IncomingStreamTrack) GetEncoding(encodingID string) *Encoding { 424 | 425 | for _, encoding := range i.encodings { 426 | if encoding.id == encodingID { 427 | return encoding 428 | } 429 | } 430 | return nil 431 | } 432 | 433 | // Attached Signal that this track has been attached. 434 | func (i *IncomingStreamTrack) Attached() { 435 | 436 | i.counter = i.counter + 1 437 | 438 | if i.counter == 1 { 439 | for _, attach := range i.onAttachedListeners { 440 | attach() 441 | } 442 | } 443 | } 444 | 445 | // Refresh Request an intra refres 446 | func (i *IncomingStreamTrack) Refresh() { 447 | 448 | for _, encoding := range i.encodings { 449 | //Request an iframe on main ssrc 450 | i.receiver.SendPLI(encoding.source.GetMedia().GetSsrc()) 451 | } 452 | } 453 | 454 | // Detached Signal that this track has been detached. 455 | func (i *IncomingStreamTrack) Detached() { 456 | 457 | i.counter = i.counter - 1 458 | 459 | if i.counter == 0 { 460 | for _, detach := range i.onDetachedListeners { 461 | detach() 462 | } 463 | } 464 | } 465 | 466 | // OnDetach 467 | func (i *IncomingStreamTrack) OnDetach(detach func()) { 468 | i.onDetachedListeners = append(i.onDetachedListeners, detach) 469 | } 470 | 471 | // OnAttach run this func when attached 472 | func (i *IncomingStreamTrack) OnAttach(attach func()) { 473 | i.onAttachedListeners = append(i.onAttachedListeners, attach) 474 | } 475 | 476 | // OnMediaFrame callback 477 | func (i *IncomingStreamTrack) OnMediaFrame(listener func([]byte, uint64)) { 478 | 479 | if i.mediaframeMultiplexer == nil { 480 | i.mediaframeMultiplexer = NewMediaFrameMultiplexer(i) 481 | } 482 | 483 | i.mediaframeMultiplexer.SetMediaFrameListener(listener) 484 | } 485 | 486 | // Stop Removes the track from the incoming stream and also detaches any attached outgoing track or recorder 487 | func (i *IncomingStreamTrack) Stop() { 488 | 489 | if i.receiver == nil { 490 | return 491 | } 492 | 493 | if i.mediaframeMultiplexer != nil { 494 | i.mediaframeMultiplexer.Stop() 495 | i.mediaframeMultiplexer = nil 496 | } 497 | 498 | for _, encoding := range i.encodings { 499 | if encoding.depacketizer != nil { 500 | encoding.depacketizer.Stop() 501 | native.DeleteStreamTrackDepacketizer(encoding.depacketizer) 502 | } 503 | if encoding.source != nil { 504 | native.DeleteRTPIncomingSourceGroup(encoding.source) 505 | } 506 | } 507 | 508 | i.encodings = nil 509 | 510 | i.receiver = nil 511 | } 512 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /transport.go: -------------------------------------------------------------------------------- 1 | package mediaserver 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/gofrs/uuid" 8 | native "github.com/notedit/media-server-go/wrapper" 9 | "github.com/notedit/sdp" 10 | ) 11 | 12 | type senderSideEstimatorListener interface { 13 | native.SenderSideEstimatorListener 14 | deleteSenderSideEstimatorListener() 15 | } 16 | 17 | type goSenderSideEstimatorListener struct { 18 | native.SenderSideEstimatorListener 19 | } 20 | 21 | func (r *goSenderSideEstimatorListener) deleteSenderSideEstimatorListener() { 22 | native.DeleteDirectorSenderSideEstimatorListener(r.SenderSideEstimatorListener) 23 | } 24 | 25 | type overwrittenSenderSideEstimatorListener struct { 26 | p native.SenderSideEstimatorListener 27 | } 28 | 29 | func (p *overwrittenSenderSideEstimatorListener) OnTargetBitrateRequested(bitrate uint) { 30 | fmt.Println(bitrate) 31 | } 32 | 33 | type dtlsICETransportListener interface { 34 | native.DTLSICETransportListener 35 | deleteDTLSICETransportListener() 36 | } 37 | 38 | type goDTLSICETransportListener struct { 39 | native.DTLSICETransportListener 40 | } 41 | 42 | func (d *goDTLSICETransportListener) deleteDTLSICETransportListener() { 43 | native.DeleteDTLSICETransportListener(d.DTLSICETransportListener) 44 | } 45 | 46 | type overwrittenDTLSICETransportListener struct { 47 | p native.DTLSICETransportListener 48 | } 49 | 50 | func (p *overwrittenDTLSICETransportListener) OnDTLSStateChange(state uint) { 51 | fmt.Println("OnDTLSStateChange", state) 52 | } 53 | 54 | type ( 55 | // TransportStopListener listener 56 | TransportStopListener func() 57 | // IncomingTrackListener new track listener 58 | IncomingTrackListener func(*IncomingStreamTrack, *IncomingStream) 59 | // OutgoingTrackListener new track listener 60 | OutgoingTrackListener func(*OutgoingStreamTrack, *OutgoingStream) 61 | // DTLSStateListener listener 62 | DTLSStateListener func(state string) 63 | ) 64 | 65 | // ICEStats ice stats for this connection 66 | type ICEStats struct { 67 | RequestsSent int64 68 | RequestsReceived int64 69 | ResponsesSent int64 70 | ResponsesReceived int64 71 | } 72 | 73 | // Transport represent a connection between a local ICE candidate and a remote set of ICE candidates over a single DTLS session 74 | type Transport struct { 75 | localIce *sdp.ICEInfo 76 | localDtls *sdp.DTLSInfo 77 | localCandidates []*sdp.CandidateInfo 78 | remoteIce *sdp.ICEInfo 79 | remoteDtls *sdp.DTLSInfo 80 | remoteCandidates []*sdp.CandidateInfo 81 | bundle native.RTPBundleTransport 82 | transport native.DTLSICETransport 83 | connection native.RTPBundleTransportConnection 84 | dtlsState string 85 | 86 | username string 87 | incomingStreams map[string]*IncomingStream 88 | outgoingStreams map[string]*OutgoingStream 89 | incomingStreamTracks map[string]*IncomingStreamTrack 90 | outgoingStreamTracks map[string]*OutgoingStreamTrack 91 | 92 | iceStats *ICEStats 93 | 94 | senderSideListener senderSideEstimatorListener 95 | dtlsICEListener dtlsICETransportListener 96 | outDTLSStateListener DTLSStateListener 97 | onIncomingTrackListeners []IncomingTrackListener 98 | onOutgoingTrackListeners []OutgoingTrackListener 99 | sync.Mutex 100 | } 101 | 102 | // NewTransport create a new transport 103 | func NewTransport(bundle native.RTPBundleTransport, remoteIce *sdp.ICEInfo, remoteDtls *sdp.DTLSInfo, remoteCandidates []*sdp.CandidateInfo, 104 | localIce *sdp.ICEInfo, localDtls *sdp.DTLSInfo, localCandidates []*sdp.CandidateInfo, disableSTUNKeepAlive bool) *Transport { 105 | 106 | transport := new(Transport) 107 | transport.remoteIce = remoteIce 108 | transport.remoteDtls = remoteDtls 109 | transport.localIce = localIce 110 | transport.localDtls = localDtls 111 | transport.bundle = bundle 112 | transport.dtlsState = "new" 113 | 114 | properties := native.NewPropertiesFacade() 115 | 116 | properties.SetPropertyStr("ice.localUsername", localIce.GetUfrag()) 117 | properties.SetPropertyStr("ice.localPassword", localIce.GetPassword()) 118 | properties.SetPropertyStr("ice.remoteUsername", remoteIce.GetUfrag()) 119 | properties.SetPropertyStr("ice.remotePassword", remoteIce.GetPassword()) 120 | 121 | properties.SetPropertyStr("dtls.setup", remoteDtls.GetSetup().String()) 122 | properties.SetPropertyStr("dtls.hash", remoteDtls.GetHash()) 123 | properties.SetPropertyStr("dtls.fingerprint", remoteDtls.GetFingerprint()) 124 | 125 | stunKeepAlive := false 126 | if disableSTUNKeepAlive { 127 | stunKeepAlive = true 128 | } 129 | 130 | properties.SetPropertyBool("disableSTUNKeepAlive", stunKeepAlive) 131 | 132 | transport.username = localIce.GetUfrag() + ":" + remoteIce.GetUfrag() 133 | transport.connection = bundle.AddICETransport(transport.username, properties) 134 | transport.transport = transport.connection.GetTransport() 135 | 136 | transport.iceStats = &ICEStats{} 137 | 138 | native.DeletePropertiesFacade(properties) 139 | 140 | sseListener := &overwrittenSenderSideEstimatorListener{} 141 | p := native.NewDirectorSenderSideEstimatorListener(sseListener) 142 | sseListener.p = p 143 | 144 | transport.senderSideListener = &goSenderSideEstimatorListener{SenderSideEstimatorListener: p} 145 | transport.transport.SetSenderSideEstimatorListener(transport.senderSideListener) 146 | 147 | dtlsListener := &overwrittenDTLSICETransportListener{} 148 | dtlsl := native.NewDirectorDTLSICETransportListener(dtlsListener) 149 | dtlsListener.p = dtlsl 150 | 151 | transport.dtlsICEListener = &goDTLSICETransportListener{DTLSICETransportListener: dtlsl} 152 | transport.transport.SetListener(transport.dtlsICEListener) 153 | 154 | var address string 155 | var port int 156 | for _, candidate := range remoteCandidates { 157 | if candidate.GetType() == "relay" { 158 | address = candidate.GetRelAddr() 159 | port = candidate.GetRelPort() 160 | } else { 161 | address = candidate.GetAddress() 162 | port = candidate.GetPort() 163 | } 164 | bundle.AddRemoteCandidate(transport.username, address, uint16(port)) 165 | } 166 | 167 | transport.localCandidates = localCandidates 168 | transport.remoteCandidates = remoteCandidates 169 | 170 | transport.incomingStreams = make(map[string]*IncomingStream) 171 | transport.outgoingStreams = make(map[string]*OutgoingStream) 172 | 173 | transport.incomingStreamTracks = make(map[string]*IncomingStreamTrack) 174 | transport.outgoingStreamTracks = make(map[string]*OutgoingStreamTrack) 175 | 176 | transport.onIncomingTrackListeners = make([]IncomingTrackListener, 0) 177 | transport.onOutgoingTrackListeners = make([]OutgoingTrackListener, 0) 178 | 179 | return transport 180 | } 181 | 182 | // Dump dump incoming and outgoint rtp and rtcp packets into a pcap file 183 | func (t *Transport) Dump(filename string, incoming bool, outgoing bool, rtcp bool) bool { 184 | ret := t.transport.Dump(filename, incoming, outgoing, rtcp) 185 | if ret == 0 { 186 | return false 187 | } 188 | return true 189 | } 190 | 191 | // SetBandwidthProbing Enable/Disable bitrate probing 192 | // This will send padding only RTX packets to allow bandwidth estimation algortithm to probe bitrate beyonf current sent values. 193 | // The ammoung of probing bitrate would be limited by the sender bitrate estimation and the limit set on the setMaxProbing Bitrate. 194 | func (t *Transport) SetBandwidthProbing(probe bool) { 195 | t.transport.SetBandwidthProbing(probe) 196 | } 197 | 198 | // SetMaxProbingBitrate Set the maximum bitrate to be used if probing is enabled. 199 | func (t *Transport) SetMaxProbingBitrate(bitrate uint) { 200 | t.transport.SetMaxProbingBitrate(bitrate) 201 | } 202 | 203 | // GetDTLSState get dtls state 204 | func (t *Transport) GetDTLSState() string { 205 | return t.dtlsState 206 | } 207 | 208 | // GetICEStats get ice stats 209 | func (t *Transport) GetICEStats() *ICEStats { 210 | 211 | t.iceStats.RequestsSent = t.connection.GetIceRequestsSent() 212 | t.iceStats.RequestsReceived = t.connection.GetIceRequestsReceived() 213 | t.iceStats.ResponsesSent = t.connection.GetIceResponsesSent() 214 | t.iceStats.ResponsesReceived = t.connection.GetIceResponsesReceived() 215 | 216 | return t.iceStats 217 | } 218 | 219 | // SetRemoteProperties Set remote RTP properties 220 | func (t *Transport) SetRemoteProperties(audio *sdp.MediaInfo, video *sdp.MediaInfo) { 221 | properties := native.NewPropertiesFacade() 222 | defer native.DeletePropertiesFacade(properties) 223 | if audio != nil { 224 | num := 0 225 | for _, codec := range audio.GetCodecs() { 226 | item := fmt.Sprintf("audio.codecs.%d", num) 227 | properties.SetPropertyStr(item+".codec", codec.GetCodec()) 228 | properties.SetPropertyInt(item+".pt", codec.GetType()) 229 | if codec.HasRTX() { 230 | properties.SetPropertyInt(item+".rtx", codec.GetRTX()) 231 | } 232 | num = num + 1 233 | } 234 | properties.SetPropertyInt("audio.codecs.length", num) 235 | 236 | num = 0 237 | for id, uri := range audio.GetExtensions() { 238 | item := fmt.Sprintf("audio.ext.%d", num) 239 | properties.SetPropertyInt(item+".id", id) 240 | properties.SetPropertyStr(item+".uri", uri) 241 | num = num + 1 242 | } 243 | properties.SetPropertyInt("audio.ext.length", num) 244 | } 245 | 246 | if video != nil { 247 | num := 0 248 | for _, codec := range video.GetCodecs() { 249 | item := fmt.Sprintf("video.codecs.%d", num) 250 | properties.SetPropertyStr(item+".codec", codec.GetCodec()) 251 | properties.SetPropertyInt(item+".pt", codec.GetType()) 252 | if codec.HasRTX() { 253 | properties.SetPropertyInt(item+".rtx", codec.GetRTX()) 254 | } 255 | num = num + 1 256 | } 257 | properties.SetPropertyInt("video.codecs.length", num) 258 | 259 | num = 0 260 | for id, uri := range video.GetExtensions() { 261 | item := fmt.Sprintf("video.ext.%d", num) 262 | properties.SetPropertyInt(item+".id", id) 263 | properties.SetPropertyStr(item+".uri", uri) 264 | num = num + 1 265 | } 266 | properties.SetPropertyInt("video.ext.length", num) 267 | } 268 | 269 | t.transport.SetRemoteProperties(properties) 270 | 271 | 272 | } 273 | 274 | // SetLocalProperties Set local RTP properties 275 | func (t *Transport) SetLocalProperties(audio *sdp.MediaInfo, video *sdp.MediaInfo) { 276 | 277 | properties := native.NewPropertiesFacade() 278 | defer native.DeletePropertiesFacade(properties) 279 | 280 | if audio != nil { 281 | num := 0 282 | for _, codec := range audio.GetCodecs() { 283 | item := fmt.Sprintf("audio.codecs.%d", num) 284 | properties.SetPropertyStr(item+".codec", codec.GetCodec()) 285 | properties.SetPropertyInt(item+".pt", codec.GetType()) 286 | if codec.HasRTX() { 287 | properties.SetPropertyInt(item+".rtx", codec.GetRTX()) 288 | } 289 | num = num + 1 290 | } 291 | properties.SetPropertyInt("audio.codecs.length", num) 292 | num = 0 293 | for id, uri := range audio.GetExtensions() { 294 | item := fmt.Sprintf("audio.ext.%d", num) 295 | properties.SetPropertyInt(item+".id", id) 296 | properties.SetPropertyStr(item+".uri", uri) 297 | num = num + 1 298 | } 299 | properties.SetPropertyInt("audio.ext.length", num) 300 | } 301 | 302 | if video != nil { 303 | num := 0 304 | for _, codec := range video.GetCodecs() { 305 | item := fmt.Sprintf("video.codecs.%d", num) 306 | properties.SetPropertyStr(item+".codec", codec.GetCodec()) 307 | properties.SetPropertyInt(item+".pt", codec.GetType()) 308 | if codec.HasRTX() { 309 | properties.SetPropertyInt(item+".rtx", codec.GetRTX()) 310 | } 311 | num = num + 1 312 | } 313 | properties.SetPropertyInt("video.codecs.length", num) 314 | num = 0 315 | for id, uri := range video.GetExtensions() { 316 | item := fmt.Sprintf("video.ext.%d", num) 317 | properties.SetPropertyInt(item+".id", id) 318 | properties.SetPropertyStr(item+".uri", uri) 319 | num = num + 1 320 | } 321 | properties.SetPropertyInt("video.ext.length", num) 322 | } 323 | 324 | t.transport.SetLocalProperties(properties) 325 | } 326 | 327 | // GetLocalDTLSInfo Get transport local DTLS info 328 | func (t *Transport) GetLocalDTLSInfo() *sdp.DTLSInfo { 329 | 330 | return t.localDtls 331 | } 332 | 333 | // GetLocalICEInfo Get transport local ICE info 334 | func (t *Transport) GetLocalICEInfo() *sdp.ICEInfo { 335 | 336 | return t.localIce 337 | } 338 | 339 | // GetLocalCandidates Get local ICE candidates for this transport 340 | func (t *Transport) GetLocalCandidates() []*sdp.CandidateInfo { 341 | 342 | return t.localCandidates 343 | } 344 | 345 | // GetRemoteCandidates Get remote ICE candidates for this transport 346 | func (t *Transport) GetRemoteCandidates() []*sdp.CandidateInfo { 347 | return t.remoteCandidates 348 | } 349 | 350 | // AddRemoteCandidate register a remote candidate info. Only needed for ice-lite to ice-lite endpoints 351 | func (t *Transport) AddRemoteCandidate(candidate *sdp.CandidateInfo) { 352 | 353 | var address string 354 | var port int 355 | 356 | if candidate.GetType() == "relay" { 357 | address = candidate.GetRelAddr() 358 | port = candidate.GetRelPort() 359 | } else { 360 | address = candidate.GetAddress() 361 | port = candidate.GetPort() 362 | } 363 | 364 | if t.bundle.AddRemoteCandidate(t.username, address, uint16(port)) != 0 { 365 | return 366 | } 367 | 368 | t.remoteCandidates = append(t.remoteCandidates, candidate) 369 | } 370 | 371 | // CreateOutgoingStream Create new outgoing stream in this transport using StreamInfo 372 | func (t *Transport) CreateOutgoingStream(streamInfo *sdp.StreamInfo) *OutgoingStream { 373 | 374 | if _, ok := t.outgoingStreams[streamInfo.GetID()]; ok { 375 | return nil 376 | } 377 | 378 | info := streamInfo.Clone() 379 | outgoingStream := NewOutgoingStream(t.transport, info) 380 | 381 | t.Lock() 382 | t.outgoingStreams[outgoingStream.GetID()] = outgoingStream 383 | t.Unlock() 384 | 385 | outgoingStream.OnTrack(func(track *OutgoingStreamTrack) { 386 | for _, trackFunc := range t.onOutgoingTrackListeners { 387 | trackFunc(track, outgoingStream) 388 | } 389 | }) 390 | 391 | for _, track := range outgoingStream.GetTracks() { 392 | for _, trackFunc := range t.onOutgoingTrackListeners { 393 | trackFunc(track, outgoingStream) 394 | } 395 | } 396 | 397 | return outgoingStream 398 | } 399 | 400 | // CreateOutgoingStreamWithID alias CreateOutgoingStream 401 | func (t *Transport) CreateOutgoingStreamWithID(streamID string, audio bool, video bool) *OutgoingStream { 402 | 403 | streamInfo := sdp.NewStreamInfo(streamID) 404 | if audio { 405 | audioTrack := sdp.NewTrackInfo(uuid.Must(uuid.NewV4()).String(), "audio") 406 | ssrc := NextSSRC() 407 | audioTrack.AddSSRC(ssrc) 408 | streamInfo.AddTrack(audioTrack) 409 | } 410 | 411 | if video { 412 | videoTrack := sdp.NewTrackInfo(uuid.Must(uuid.NewV4()).String(), "video") 413 | ssrc := NextSSRC() 414 | videoTrack.AddSSRC(ssrc) 415 | streamInfo.AddTrack(videoTrack) 416 | } 417 | 418 | stream := t.CreateOutgoingStream(streamInfo) 419 | return stream 420 | } 421 | 422 | // CreateOutgoingStreamTrack Create new outgoing track in this transport 423 | func (t *Transport) CreateOutgoingStreamTrack(media string, trackId string, ssrcs map[string]uint) *OutgoingStreamTrack { 424 | 425 | var mediaType native.MediaFrameType = 0 426 | if media == "video" { 427 | mediaType = 1 428 | } 429 | 430 | if trackId == "" { 431 | trackId = uuid.Must(uuid.NewV4()).String() 432 | } 433 | 434 | source := native.NewRTPOutgoingSourceGroup(mediaType) 435 | 436 | if ssrc, ok := ssrcs["media"]; ok { 437 | source.GetMedia().SetSsrc(ssrc) 438 | } else { 439 | source.GetMedia().SetSsrc(NextSSRC()) 440 | } 441 | 442 | if ssrc, ok := ssrcs["rtx"]; ok { 443 | source.GetRtx().SetSsrc(ssrc) 444 | } else { 445 | source.GetRtx().SetSsrc(NextSSRC()) 446 | } 447 | 448 | if ssrc, ok := ssrcs["fec"]; ok { 449 | source.GetFec().SetSsrc(ssrc) 450 | } else { 451 | source.GetFec().SetSsrc(NextSSRC()) 452 | } 453 | 454 | // todo error handle 455 | t.transport.AddOutgoingSourceGroup(source) 456 | 457 | outgoingTrack := newOutgoingStreamTrack(media, trackId, native.TransportToSender(t.transport), source) 458 | 459 | for _, trackFunc := range t.onOutgoingTrackListeners { 460 | trackFunc(outgoingTrack, nil) 461 | } 462 | 463 | return outgoingTrack 464 | } 465 | 466 | // CreateIncomingStream Create an incoming stream object from the media stream info objet 467 | func (t *Transport) CreateIncomingStream(streamInfo *sdp.StreamInfo) *IncomingStream { 468 | 469 | if _, ok := t.incomingStreams[streamInfo.GetID()]; ok { 470 | return nil 471 | } 472 | 473 | incomingStream := newIncomingStream(t.transport, native.TransportToReceiver(t.transport), streamInfo) 474 | 475 | t.Lock() 476 | t.incomingStreams[incomingStream.GetID()] = incomingStream 477 | t.Unlock() 478 | 479 | return incomingStream 480 | } 481 | 482 | // CreateIncomingStreamTrack Create new incoming stream in this transport. TODO: Simulcast is still not supported 483 | // You can use IncomingStream's CreateTrack 484 | func (t *Transport) CreateIncomingStreamTrack(media string, trackId string, ssrcs map[string]uint) *IncomingStreamTrack { 485 | 486 | var mediaType native.MediaFrameType = 0 487 | if media == "video" { 488 | mediaType = 1 489 | } 490 | 491 | if trackId == "" { 492 | trackId = uuid.Must(uuid.NewV4()).String() 493 | } 494 | 495 | source := native.NewRTPIncomingSourceGroup(mediaType, t.transport.GetTimeService()) 496 | 497 | if ssrc, ok := ssrcs["media"]; ok { 498 | source.GetMedia().SetSsrc(ssrc) 499 | } else { 500 | source.GetMedia().SetSsrc(NextSSRC()) 501 | } 502 | 503 | if ssrc, ok := ssrcs["rtx"]; ok { 504 | source.GetRtx().SetSsrc(ssrc) 505 | } else { 506 | source.GetRtx().SetSsrc(NextSSRC()) 507 | } 508 | 509 | if ssrc, ok := ssrcs["fec"]; ok { 510 | source.GetFec().SetSsrc(ssrc) 511 | } else { 512 | source.GetFec().SetSsrc(NextSSRC()) 513 | } 514 | 515 | t.transport.AddIncomingSourceGroup(source) 516 | 517 | sources := map[string]native.RTPIncomingSourceGroup{"": source} 518 | 519 | incomingTrack := NewIncomingStreamTrack(media, trackId, native.TransportToReceiver(t.transport), sources) 520 | 521 | for _, trackFunc := range t.onIncomingTrackListeners { 522 | trackFunc(incomingTrack, nil) 523 | } 524 | 525 | return incomingTrack 526 | } 527 | 528 | func (t *Transport) RemoveIncomingStream(incomingStream *IncomingStream) { 529 | 530 | t.Lock() 531 | delete(t.incomingStreams, incomingStream.GetID()) 532 | t.Unlock() 533 | } 534 | 535 | // GetIncomingStreams get all incoming streams 536 | func (t *Transport) GetIncomingStreams() []*IncomingStream { 537 | incomings := []*IncomingStream{} 538 | for _, stream := range t.incomingStreams { 539 | incomings = append(incomings, stream) 540 | } 541 | return incomings 542 | } 543 | 544 | // GetIncomingStream get one incoming stream 545 | func (t *Transport) GetIncomingStream(streamId string) *IncomingStream { 546 | t.Lock() 547 | defer t.Unlock() 548 | return t.incomingStreams[streamId] 549 | } 550 | 551 | // GetOutgoingStreams get all outgoing streams 552 | func (t *Transport) GetOutgoingStreams() []*OutgoingStream { 553 | outgoings := []*OutgoingStream{} 554 | for _, stream := range t.outgoingStreams { 555 | outgoings = append(outgoings, stream) 556 | } 557 | return outgoings 558 | } 559 | 560 | // GetOutgoingStream get one outgoing stream 561 | func (t *Transport) GetOutgoingStream(streamId string) *OutgoingStream { 562 | t.Lock() 563 | defer t.Unlock() 564 | return t.outgoingStreams[streamId] 565 | } 566 | 567 | 568 | // OnIncomingTrack register incoming track 569 | func (t *Transport) OnIncomingTrack(listener IncomingTrackListener) { 570 | t.Lock() 571 | defer t.Unlock() 572 | t.onIncomingTrackListeners = append(t.onIncomingTrackListeners, listener) 573 | } 574 | 575 | // OnOutgoingTrack register outgoing track 576 | func (t *Transport) OnOutgoingTrack(listener OutgoingTrackListener) { 577 | t.Lock() 578 | defer t.Unlock() 579 | t.onOutgoingTrackListeners = append(t.onOutgoingTrackListeners, listener) 580 | } 581 | 582 | // OnDTLSICEState OnDTLSICEState 583 | func (t *Transport) OnDTLSICEState(listener DTLSStateListener) { 584 | t.Lock() 585 | defer t.Unlock() 586 | t.outDTLSStateListener = listener 587 | } 588 | 589 | 590 | func (t *Transport) GetLastActiveTime() uint64 { 591 | 592 | return t.transport.GetLastActiveTime() 593 | } 594 | 595 | 596 | // Stop stop this transport 597 | func (t *Transport) Stop() { 598 | 599 | if t.bundle == nil { 600 | return 601 | } 602 | 603 | for _, incoming := range t.incomingStreams { 604 | incoming.Stop() 605 | } 606 | 607 | for _, outgoing := range t.outgoingStreams { 608 | outgoing.Stop() 609 | } 610 | 611 | if t.senderSideListener != nil { 612 | t.senderSideListener.deleteSenderSideEstimatorListener() 613 | t.senderSideListener = nil 614 | } 615 | 616 | if t.dtlsICEListener != nil { 617 | t.dtlsICEListener.deleteDTLSICETransportListener() 618 | t.dtlsICEListener = nil 619 | } 620 | 621 | t.bundle.RemoveICETransport(t.username) 622 | 623 | t.incomingStreams = nil 624 | t.outgoingStreams = nil 625 | 626 | t.connection = nil 627 | t.transport = nil 628 | 629 | t.username = "" 630 | t.bundle = nil 631 | 632 | } 633 | -------------------------------------------------------------------------------- /wrapper/mediaserver.i: -------------------------------------------------------------------------------- 1 | %module(directors="1") native 2 | %{ 3 | 4 | #include 5 | #include 6 | #include 7 | #include "../media-server/include/config.h" 8 | #include "../media-server/include/dtls.h" 9 | #include "../media-server/include/OpenSSL.h" 10 | #include "../media-server/include/media.h" 11 | #include "../media-server/include/video.h" 12 | #include "../media-server/include/audio.h" 13 | #include "../media-server/include/rtp.h" 14 | #include "../media-server/include/tools.h" 15 | #include "../media-server/include/rtpsession.h" 16 | #include "../media-server/include/DTLSICETransport.h" 17 | #include "../media-server/include/RTPBundleTransport.h" 18 | #include "../media-server/include/mp4recorder.h" 19 | #include "../media-server/include/mp4streamer.h" 20 | #include "../media-server/include/rtp/RTPStreamTransponder.h" 21 | #include "../media-server/include/ActiveSpeakerDetector.h" 22 | #include "../media-server/include/EventLoop.h" 23 | 24 | 25 | using RTPBundleTransportConnection = RTPBundleTransport::Connection; 26 | using MediaFrameListener = MediaFrame::Listener; 27 | 28 | 29 | class PropertiesFacade : private Properties 30 | { 31 | public: 32 | void SetPropertyInt(const char* key,int intval) 33 | { 34 | Properties::SetProperty(key,intval); 35 | } 36 | void SetPropertyStr(const char* key,const char* val) 37 | { 38 | Properties::SetProperty(key,val); 39 | } 40 | void SetPropertyBool(const char* key,bool boolval) 41 | { 42 | Properties::SetProperty(key,boolval); 43 | } 44 | }; 45 | 46 | 47 | class MediaServer 48 | { 49 | 50 | public: 51 | 52 | static void Initialize() 53 | { 54 | //Initialize ssl 55 | OpenSSL::ClassInit(); 56 | 57 | //Start DTLS 58 | DTLSConnection::Initialize(); 59 | } 60 | 61 | static void EnableLog(bool flag) 62 | { 63 | //Enable log 64 | Log("-EnableLog [%d]\n",flag); 65 | Logger::EnableLog(flag); 66 | } 67 | 68 | static void EnableDebug(bool flag) 69 | { 70 | //Enable debug 71 | Logger::EnableDebug(flag); 72 | } 73 | 74 | static void EnableUltraDebug(bool flag) 75 | { 76 | //Enable debug 77 | Log("-EnableUltraDebug [%d]\n",flag); 78 | Logger::EnableUltraDebug(flag); 79 | } 80 | 81 | static bool SetPortRange(int minPort, int maxPort) 82 | { 83 | return RTPTransport::SetPortRange(minPort,maxPort); 84 | } 85 | 86 | static std::string GetFingerprint() 87 | { 88 | return DTLSConnection::GetCertificateFingerPrint(DTLSConnection::Hash::SHA256); 89 | } 90 | }; 91 | 92 | 93 | class RTPSessionFacade : 94 | public RTPSender, 95 | public RTPReceiver, 96 | public RTPSession 97 | { 98 | public: 99 | RTPSessionFacade(MediaFrame::Type media) : RTPSession(media,NULL) 100 | { 101 | //Delegate to group 102 | delegate = true; 103 | //Start group dispatch 104 | GetIncomingSourceGroup()->Start(); 105 | } 106 | virtual ~RTPSessionFacade() = default; 107 | 108 | virtual int Enqueue(const RTPPacket::shared& packet) { return SendPacket(packet); } 109 | virtual int Enqueue(const RTPPacket::shared& packet,std::function modifier) { return SendPacket(modifier(packet)); } 110 | virtual int SendPLI(DWORD ssrc) { return RequestFPU();} 111 | 112 | int Init(const Properties &properties) 113 | { 114 | RTPMap rtp; 115 | RTPMap apt; 116 | 117 | //Get codecs 118 | std::vector codecs; 119 | properties.GetChildrenArray("codecs",codecs); 120 | 121 | //For each codec 122 | for (auto it = codecs.begin(); it!=codecs.end(); ++it) 123 | { 124 | 125 | BYTE codec; 126 | //Depending on the type 127 | switch (GetMediaType()) 128 | { 129 | case MediaFrame::Audio: 130 | codec = (BYTE)AudioCodec::GetCodecForName(it->GetProperty("codec")); 131 | break; 132 | case MediaFrame::Video: 133 | codec = (BYTE)VideoCodec::GetCodecForName(it->GetProperty("codec")); 134 | break; 135 | default: 136 | //skip 137 | continue; 138 | 139 | } 140 | 141 | if (codec == (BYTE)-1) { 142 | continue; 143 | } 144 | 145 | //Get codec type 146 | BYTE type = it->GetProperty("pt",0); 147 | //ADD it 148 | rtp[type] = codec; 149 | } 150 | 151 | //Set local 152 | RTPSession::SetSendingRTPMap(rtp,apt); 153 | RTPSession::SetReceivingRTPMap(rtp,apt); 154 | 155 | //Call parent 156 | return RTPSession::Init(); 157 | } 158 | }; 159 | 160 | 161 | class MP4RecorderFacade : 162 | public MP4Recorder, 163 | public MP4Recorder::Listener 164 | { 165 | public: 166 | MP4RecorderFacade() : 167 | MP4Recorder(this) 168 | { 169 | 170 | } 171 | 172 | void onFirstFrame(QWORD time) override 173 | { 174 | // todo 175 | } 176 | 177 | void onClosed() override 178 | { 179 | // todo 180 | } 181 | }; 182 | 183 | 184 | class ActiveTrackListener { 185 | public: 186 | ActiveTrackListener() 187 | { 188 | 189 | } 190 | virtual ~ActiveTrackListener() { 191 | 192 | } 193 | virtual void onActiveTrackchanged(uint32_t id){ 194 | 195 | } 196 | }; 197 | 198 | 199 | 200 | 201 | class MediaFrameSessionFacade : 202 | public RTPReceiver 203 | { 204 | public: 205 | MediaFrameSessionFacade(MediaFrame::Type media): 206 | source(media,loop) 207 | { 208 | 209 | loop.Start(-1); 210 | source.Start(); 211 | mediatype = media; 212 | } 213 | int Init(const Properties &properties) 214 | { 215 | //Get codecs 216 | std::vector codecs; 217 | properties.GetChildrenArray("codecs",codecs); 218 | 219 | //For each codec 220 | for (auto it = codecs.begin(); it!=codecs.end(); ++it) 221 | { 222 | 223 | BYTE codec; 224 | //Depending on the type 225 | switch (mediatype) 226 | { 227 | case MediaFrame::Audio: 228 | codec = (BYTE)AudioCodec::GetCodecForName(it->GetProperty("codec")); 229 | audioCodec = codec; 230 | break; 231 | case MediaFrame::Video: 232 | codec = (BYTE)VideoCodec::GetCodecForName(it->GetProperty("codec")); 233 | videoCodec = codec; 234 | break; 235 | default: 236 | ///Ignore 237 | codec = (BYTE)-1; 238 | break; 239 | } 240 | 241 | //Get codec type 242 | BYTE type = it->GetProperty("pt",0); 243 | //ADD it 244 | rtp[type] = codec; 245 | } 246 | 247 | return 1; 248 | } 249 | 250 | void onRTPPacket(uint8_t* data, int size) 251 | { 252 | 253 | 254 | // Run on thread 255 | loop.Async([=](...) { 256 | 257 | RTPHeader header; 258 | RTPHeaderExtension extension; 259 | 260 | int lsize = size; 261 | int len = header.Parse(data,lsize); 262 | 263 | if (!len) 264 | { 265 | //Debug 266 | Debug("-MediaFrameSessionFacade::onRTPPacket() | Could not parse RTP header\n"); 267 | return; 268 | } 269 | 270 | if (header.extension) 271 | { 272 | 273 | //Parse extension 274 | int l = extension.Parse(extMap,data+len,lsize-len); 275 | //If not parsed 276 | if (!l) 277 | { 278 | ///Debug 279 | Debug("-MediaFrameSessionFacade::onRTPPacket() | Could not parse RTP header extension\n"); 280 | //Exit 281 | return; 282 | } 283 | //Inc ini 284 | len += l; 285 | } 286 | 287 | if (header.padding) 288 | { 289 | //Get last 2 bytes 290 | WORD padding = get1(data,lsize-1); 291 | //Ensure we have enought size 292 | if (size-len(mediatype,codec,header,extension); 319 | 320 | //Set the payload 321 | packet->SetPayload(data+len,lsize-len); 322 | 323 | WORD seq = packet->GetSeqNum(); 324 | 325 | source.media.SetSeqNum(seq); 326 | 327 | if (source.media.ssrc != ssrc) { 328 | source.media.Reset(); 329 | source.media.ssrc = ssrc; 330 | } 331 | 332 | source.media.Update(getTimeMS(),packet->GetSeqNum(),packet->GetRTPHeader().GetSize()+packet->GetMediaLength()); 333 | 334 | WORD cycles = source.media.SetSeqNum(packet->GetSeqNum()); 335 | //Set cycles back 336 | packet->SetSeqCycles(cycles); 337 | 338 | source.AddPacket(packet,0); 339 | 340 | }); 341 | } 342 | 343 | void onRTPData(uint8_t* data, int size, uint32_t timestamp) 344 | { 345 | 346 | } 347 | 348 | RTPIncomingSourceGroup* GetIncomingSourceGroup() 349 | { 350 | return &source; 351 | } 352 | int End() 353 | { 354 | Log("MediaFrameSessionFacade End\n"); 355 | return 1; 356 | } 357 | virtual int SendPLI(DWORD ssrc) { 358 | return 1; 359 | } 360 | 361 | private: 362 | RTPMap extMap; 363 | RTPMap rtp; 364 | RTPMap apt; 365 | BYTE audioCodec; 366 | BYTE videoCodec; 367 | 368 | DWORD ssrc = 0; 369 | DWORD extSeqNum = 0; 370 | 371 | MediaFrame::Type mediatype; 372 | EventLoop loop; 373 | RTPIncomingSourceGroup source; 374 | }; 375 | 376 | 377 | 378 | 379 | class RTPSenderFacade 380 | { 381 | public: 382 | RTPSenderFacade(DTLSICETransport* transport) 383 | { 384 | sender = transport; 385 | } 386 | 387 | RTPSenderFacade(RTPSessionFacade* session) 388 | { 389 | sender = session; 390 | } 391 | 392 | RTPSender* get() { return sender;} 393 | private: 394 | RTPSender* sender; 395 | }; 396 | 397 | class RTPReceiverFacade 398 | { 399 | public: 400 | RTPReceiverFacade(DTLSICETransport* transport) 401 | { 402 | receiver = transport; 403 | } 404 | 405 | RTPReceiverFacade(RTPSessionFacade* session) 406 | { 407 | receiver = session; 408 | } 409 | 410 | RTPReceiverFacade(MediaFrameSessionFacade* session) 411 | { 412 | receiver = session; 413 | } 414 | 415 | 416 | int SendPLI(DWORD ssrc) 417 | { 418 | return receiver ? receiver->SendPLI(ssrc) : 0; 419 | } 420 | 421 | RTPReceiver* get() { return receiver;} 422 | private: 423 | RTPReceiver* receiver; 424 | }; 425 | 426 | 427 | RTPSenderFacade* TransportToSender(DTLSICETransport* transport) 428 | { 429 | return new RTPSenderFacade(transport); 430 | } 431 | RTPReceiverFacade* TransportToReceiver(DTLSICETransport* transport) 432 | { 433 | return new RTPReceiverFacade(transport); 434 | } 435 | 436 | RTPSenderFacade* SessionToSender(RTPSessionFacade* session) 437 | { 438 | return new RTPSenderFacade(session); 439 | } 440 | 441 | RTPReceiverFacade* SessionToReceiver(RTPSessionFacade* session) 442 | { 443 | return new RTPReceiverFacade(session); 444 | } 445 | 446 | 447 | RTPReceiverFacade* RTPSessionToReceiver(MediaFrameSessionFacade* session) 448 | { 449 | return new RTPReceiverFacade(session); 450 | } 451 | 452 | 453 | 454 | 455 | class RTPStreamTransponderFacade : 456 | public RTPStreamTransponder 457 | { 458 | public: 459 | RTPStreamTransponderFacade(RTPOutgoingSourceGroup* outgoing,RTPSenderFacade* sender) : 460 | RTPStreamTransponder(outgoing, sender ? sender->get() : NULL) 461 | {} 462 | 463 | bool SetIncoming(RTPIncomingMediaStream* incoming, RTPReceiverFacade* receiver) 464 | { 465 | return RTPStreamTransponder::SetIncoming(incoming, receiver ? receiver->get() : NULL); 466 | } 467 | 468 | bool SetIncoming(RTPIncomingMediaStream* incoming, RTPReceiver* receiver) 469 | { 470 | return RTPStreamTransponder::SetIncoming(incoming, receiver); 471 | } 472 | 473 | virtual void onREMB(RTPOutgoingSourceGroup* group,DWORD ssrc, DWORD bitrate) override 474 | { 475 | // todo make callback 476 | // Log("onREMB\n"); 477 | } 478 | void SetMinPeriod(DWORD period) { this->period = period; } 479 | 480 | private: 481 | DWORD period = 1000; 482 | QWORD last = 0; 483 | }; 484 | 485 | 486 | 487 | class StreamTrackDepacketizer : 488 | public RTPIncomingMediaStream::Listener 489 | { 490 | public: 491 | StreamTrackDepacketizer(RTPIncomingMediaStream* incomingSource) 492 | { 493 | //Store 494 | this->incomingSource = incomingSource; 495 | //Add us as RTP listeners 496 | this->incomingSource->AddListener(this); 497 | //No depkacketixer yet 498 | depacketizer = NULL; 499 | } 500 | 501 | virtual ~StreamTrackDepacketizer() 502 | { 503 | //JIC 504 | Stop(); 505 | //Check 506 | if (depacketizer) 507 | //Delete depacketier 508 | delete(depacketizer); 509 | } 510 | 511 | virtual void onRTP(RTPIncomingMediaStream* group,const RTPPacket::shared& packet) 512 | { 513 | 514 | if (listeners.empty()) 515 | return; 516 | 517 | 518 | //If depacketizer is not the same codec 519 | if (depacketizer && depacketizer->GetCodec()!=packet->GetCodec()) 520 | { 521 | //Delete it 522 | delete(depacketizer); 523 | //Create it next 524 | depacketizer = NULL; 525 | } 526 | //If we don't have a depacketized 527 | if (!depacketizer) 528 | //Create one 529 | depacketizer = RTPDepacketizer::Create(packet->GetMedia(),packet->GetCodec()); 530 | //Ensure we have it 531 | if (!depacketizer) 532 | //Do nothing 533 | return; 534 | //Pass the pakcet to the depacketizer 535 | MediaFrame* frame = depacketizer->AddPacket(packet); 536 | 537 | //If we have a new frame 538 | if (frame) 539 | { 540 | //Call all listeners 541 | for (const auto& listener : listeners) 542 | //Call listener 543 | listener->onMediaFrame(packet->GetSSRC(),*frame); 544 | //Next 545 | depacketizer->ResetFrame(); 546 | } 547 | } 548 | 549 | virtual void onBye(RTPIncomingMediaStream* group) 550 | { 551 | if(depacketizer) 552 | //Skip current 553 | depacketizer->ResetFrame(); 554 | } 555 | 556 | virtual void onEnded(RTPIncomingMediaStream* group) 557 | { 558 | if (incomingSource==group) 559 | incomingSource = nullptr; 560 | } 561 | 562 | void AddMediaListener(MediaFrame::Listener *listener) 563 | { 564 | //Add to set 565 | if (!incomingSource || !listener) 566 | //Done 567 | return; 568 | //Add listener async 569 | incomingSource->GetTimeService().Async([=](...){ 570 | //Add to set 571 | listeners.insert(listener); 572 | }); 573 | } 574 | 575 | void RemoveMediaListener(MediaFrame::Listener *listener) 576 | { 577 | //Remove from set 578 | if (!incomingSource) 579 | //Done 580 | return; 581 | 582 | //Add listener sync so it can be deleted after this call 583 | incomingSource->GetTimeService().Sync([=](...){ 584 | //Remove from set 585 | listeners.erase(listener); 586 | }); 587 | } 588 | 589 | void Stop() 590 | { 591 | //If already stopped 592 | if (!incomingSource) 593 | //Done 594 | return; 595 | 596 | //Stop listeneing 597 | incomingSource->RemoveListener(this); 598 | //Clean it 599 | incomingSource = NULL; 600 | } 601 | 602 | private: 603 | std::set listeners; 604 | RTPDepacketizer* depacketizer; 605 | RTPIncomingMediaStream* incomingSource; 606 | }; 607 | 608 | 609 | 610 | class DTLSICETransportListener : 611 | public DTLSICETransport::Listener 612 | { 613 | public: 614 | DTLSICETransportListener() 615 | { 616 | 617 | } 618 | 619 | virtual ~DTLSICETransportListener() = default; 620 | 621 | virtual void onRemoteICECandidateActivated(const std::string& ip, uint16_t port, uint32_t priority) override 622 | { 623 | 624 | // todo 625 | } 626 | 627 | virtual void onDTLSStateChanged(const DTLSICETransport::DTLSState state) override 628 | { 629 | 630 | switch(state) 631 | { 632 | case DTLSICETransport::DTLSState::New: 633 | onDTLSStateChange(0); 634 | break; 635 | case DTLSICETransport::DTLSState::Connecting: 636 | onDTLSStateChange(1); 637 | break; 638 | case DTLSICETransport::DTLSState::Connected: 639 | onDTLSStateChange(2); 640 | break; 641 | case DTLSICETransport::DTLSState::Closed: 642 | onDTLSStateChange(3); 643 | break; 644 | case DTLSICETransport::DTLSState::Failed: 645 | onDTLSStateChange(4); 646 | break; 647 | } 648 | } 649 | 650 | virtual void onDTLSStateChange(uint32_t state) 651 | { 652 | 653 | } 654 | }; 655 | 656 | 657 | 658 | 659 | class SenderSideEstimatorListener : 660 | public RemoteRateEstimator::Listener 661 | { 662 | public: 663 | SenderSideEstimatorListener() 664 | { 665 | 666 | } 667 | 668 | virtual void onTargetBitrateRequested(DWORD bitrate) override 669 | { 670 | // todo make callback 671 | } 672 | 673 | private: 674 | DWORD period = 500; 675 | QWORD last = 0; 676 | }; 677 | 678 | 679 | 680 | EvenSource::EvenSource() 681 | { 682 | } 683 | 684 | EvenSource::EvenSource(const char* str) 685 | { 686 | } 687 | 688 | EvenSource::EvenSource(const std::wstring &str) 689 | { 690 | } 691 | 692 | EvenSource::~EvenSource() 693 | { 694 | } 695 | 696 | void EvenSource::SendEvent(const char* type,const char* msg,...) 697 | { 698 | } 699 | 700 | 701 | class LayerSources : public std::vector 702 | { 703 | public: 704 | size_t size() const { return std::vector::size(); } 705 | LayerSource* get(size_t i) { return std::vector::at(i); } 706 | }; 707 | 708 | 709 | 710 | class ActiveSpeakerDetectorFacade : 711 | public ActiveSpeakerDetector, 712 | public ActiveSpeakerDetector::Listener, 713 | public RTPIncomingMediaStream::Listener 714 | { 715 | public: 716 | ActiveSpeakerDetectorFacade(ActiveTrackListener* listener) : 717 | ActiveSpeakerDetector(this), 718 | listener(listener) 719 | {}; 720 | 721 | virtual void onActiveSpeakerChanded(uint32_t id) override 722 | { 723 | // todo make callback 724 | 725 | if (listener) 726 | { 727 | listener->onActiveTrackchanged(id); 728 | } 729 | } 730 | 731 | void AddIncomingSourceGroup(RTPIncomingMediaStream* incoming, uint32_t id) 732 | { 733 | if (incoming) 734 | { 735 | ScopedLock lock(mutex); 736 | //Insert new 737 | auto [it,inserted] = sources.try_emplace(incoming,id); 738 | //If already present 739 | if (!inserted) 740 | //do nothing 741 | return; 742 | //Add us as rtp listeners 743 | incoming->AddListener(this); 744 | //initialize to silence 745 | ActiveSpeakerDetector::Accumulate(id, false, 127, getTimeMS()); 746 | } 747 | } 748 | 749 | void RemoveIncomingSourceGroup(RTPIncomingMediaStream* incoming) 750 | { 751 | if (incoming) 752 | { 753 | ScopedLock lock(mutex); 754 | //Get map 755 | auto it = sources.find(incoming); 756 | //check it was present 757 | if (it==sources.end()) 758 | //Do nothing 759 | return; 760 | //Remove listener 761 | incoming->RemoveListener(this); 762 | //RElease id 763 | ActiveSpeakerDetector::Release(it->second); 764 | //Erase 765 | sources.erase(it); 766 | } 767 | } 768 | 769 | virtual void onRTP(RTPIncomingMediaStream* incoming,const RTPPacket::shared& packet) override 770 | { 771 | if (packet->HasAudioLevel()) 772 | { 773 | ScopedLock lock(mutex); 774 | //Get map 775 | auto it = sources.find(incoming); 776 | //check it was present 777 | if (it==sources.end()) 778 | //Do nothing 779 | return; 780 | //Accumulate on id 781 | ActiveSpeakerDetector::Accumulate(it->second, packet->GetVAD(),packet->GetLevel(), getTimeMS()); 782 | } 783 | } 784 | 785 | virtual void onBye(RTPIncomingMediaStream* incoming) override 786 | { 787 | 788 | } 789 | 790 | virtual void onEnded(RTPIncomingMediaStream* incoming) override 791 | { 792 | if (incoming) 793 | { 794 | ScopedLock lock(mutex); 795 | //Get map 796 | auto it = sources.find(incoming); 797 | //check it was present 798 | if (it==sources.end()) 799 | //Do nothing 800 | //Release id 801 | ActiveSpeakerDetector::Release(it->second); 802 | //Erase 803 | sources.erase(it); 804 | } 805 | } 806 | private: 807 | Mutex mutex; 808 | std::map sources; 809 | ActiveTrackListener* listener; 810 | }; 811 | 812 | 813 | class MediaFrameListenerFacade: 814 | public MediaFrameListener 815 | { 816 | public: 817 | MediaFrameListenerFacade() 818 | { 819 | 820 | } 821 | 822 | virtual void onMediaFrame(const MediaFrame &frame) { 823 | 824 | } 825 | virtual void onMediaFrame(DWORD ssrc, const MediaFrame &frame) { 826 | 827 | onMediaFrame(frame); 828 | } 829 | 830 | }; 831 | 832 | 833 | class MediaFrameMultiplexer : 834 | public RTPIncomingMediaStream::Listener 835 | { 836 | public: 837 | MediaFrameMultiplexer(RTPIncomingMediaStream* incomingSource) 838 | { 839 | //Store 840 | this->incomingSource = incomingSource; 841 | //Add us as RTP listeners 842 | this->incomingSource->AddListener(this); 843 | //No depkacketixer yet 844 | depacketizer = NULL; 845 | } 846 | 847 | virtual ~MediaFrameMultiplexer() 848 | { 849 | //JIC 850 | Stop(); 851 | //Check 852 | if (depacketizer) 853 | //Delete depacketier 854 | delete(depacketizer); 855 | } 856 | 857 | virtual void onRTP(RTPIncomingMediaStream* group,const RTPPacket::shared& packet) 858 | { 859 | 860 | if (listeners.empty()) 861 | return; 862 | 863 | 864 | if (depacketizer && depacketizer->GetCodec()!=packet->GetCodec()) 865 | { 866 | //Delete it 867 | delete(depacketizer); 868 | //Create it next 869 | depacketizer = NULL; 870 | } 871 | //If we don't have a depacketized 872 | if (!depacketizer) 873 | //Create one 874 | depacketizer = RTPDepacketizer::Create(packet->GetMedia(),packet->GetCodec()); 875 | //Ensure we have it 876 | if (!depacketizer) 877 | //Do nothing 878 | return; 879 | //Pass the pakcet to the depacketizer 880 | MediaFrame* frame = depacketizer->AddPacket(packet); 881 | 882 | //If we have a new frame 883 | if (frame) 884 | { 885 | //Call all listeners 886 | for (Listeners::const_iterator it = listeners.begin();it!=listeners.end();++it) 887 | //Call listener 888 | (*it)->onMediaFrame(*frame); 889 | //Next 890 | depacketizer->ResetFrame(); 891 | } 892 | 893 | } 894 | 895 | virtual void onBye(RTPIncomingMediaStream* group) 896 | { 897 | if(depacketizer) 898 | //Skip current 899 | depacketizer->ResetFrame(); 900 | } 901 | 902 | virtual void onEnded(RTPIncomingMediaStream* group) 903 | { 904 | if (incomingSource==group) 905 | incomingSource = nullptr; 906 | } 907 | 908 | void AddMediaListener(MediaFrameListener *listener) 909 | { 910 | //Add to set 911 | listeners.insert(listener); 912 | } 913 | 914 | void RemoveMediaListener(MediaFrameListener *listener) 915 | { 916 | //Remove from set 917 | listeners.erase(listener); 918 | } 919 | 920 | void Stop() 921 | { 922 | //If already stopped 923 | if (!incomingSource) 924 | //Done 925 | return; 926 | 927 | //Stop listeneing 928 | incomingSource->RemoveListener(this); 929 | //Clean it 930 | incomingSource = NULL; 931 | } 932 | 933 | private: 934 | typedef std::set Listeners; 935 | private: 936 | Listeners listeners; 937 | RTPDepacketizer* depacketizer; 938 | RTPIncomingMediaStream* incomingSource; 939 | }; 940 | 941 | %} 942 | 943 | 944 | %feature("director") SenderSideEstimatorListener; 945 | %feature("director") MediaFrameListenerFacade; 946 | %feature("director") ActiveTrackListener; 947 | %feature("director") DTLSICETransportListener; 948 | 949 | 950 | 951 | %include 952 | %include "stdint.i" 953 | %include "std_string.i" 954 | %include "std_vector.i" 955 | 956 | 957 | #define QWORD uint64_t 958 | #define DWORD uint32_t 959 | #define WORD uint16_t 960 | #define SWORD int16_t 961 | #define BYTE uint8_t 962 | #define SBYTE char 963 | 964 | 965 | %include "../media-server/include/acumulator.h" 966 | 967 | 968 | %{ 969 | using MediaFrameType = MediaFrame::Type; 970 | %} 971 | enum MediaFrameType; 972 | 973 | 974 | 975 | 976 | struct LayerInfo 977 | { 978 | static BYTE MaxLayerId; 979 | BYTE temporalLayerId = MaxLayerId; 980 | BYTE spatialLayerId = MaxLayerId; 981 | }; 982 | 983 | struct LayerSource : public LayerInfo 984 | { 985 | DWORD numPackets; 986 | DWORD totalBytes; 987 | DWORD bitrate; 988 | }; 989 | 990 | class LayerSources 991 | { 992 | public: 993 | size_t size() const; 994 | LayerSource* get(size_t i); 995 | }; 996 | 997 | struct RTPSource 998 | { 999 | DWORD ssrc; 1000 | DWORD extSeqNum; 1001 | DWORD cycles; 1002 | DWORD jitter; 1003 | DWORD numPackets; 1004 | DWORD numRTCPPackets; 1005 | DWORD totalBytes; 1006 | DWORD totalRTCPBytes; 1007 | DWORD bitrate; 1008 | }; 1009 | 1010 | 1011 | struct RTPIncomingSource : public RTPSource 1012 | { 1013 | DWORD lostPackets; 1014 | DWORD dropPackets; 1015 | DWORD totalPacketsSinceLastSR; 1016 | DWORD totalBytesSinceLastSR; 1017 | DWORD minExtSeqNumSinceLastSR ; 1018 | DWORD lostPacketsSinceLastSR; 1019 | QWORD lastReceivedSenderNTPTimestamp; 1020 | QWORD lastReceivedSenderReport; 1021 | QWORD lastReport; 1022 | QWORD lastPLI; 1023 | DWORD totalPLIs; 1024 | DWORD totalNACKs; 1025 | QWORD lastNACKed; 1026 | 1027 | %extend 1028 | { 1029 | LayerSources layers() 1030 | { 1031 | LayerSources layers; 1032 | for(auto it = $self->layers.begin(); it != $self->layers.end(); ++it ) 1033 | layers.push_back(&(it->second)); 1034 | return layers; 1035 | } 1036 | } 1037 | }; 1038 | 1039 | struct RTPOutgoingSource : public RTPSource 1040 | { 1041 | 1042 | DWORD time; 1043 | DWORD lastTime; 1044 | DWORD numPackets; 1045 | DWORD numRTCPPackets; 1046 | DWORD totalBytes; 1047 | DWORD totalRTCPBytes; 1048 | QWORD lastSenderReport; 1049 | QWORD lastSenderReportNTP; 1050 | }; 1051 | 1052 | 1053 | %nodefaultctor TimeService; 1054 | struct TimeService 1055 | { 1056 | 1057 | }; 1058 | 1059 | 1060 | struct RTPOutgoingSourceGroup 1061 | { 1062 | RTPOutgoingSourceGroup(MediaFrameType type); 1063 | RTPOutgoingSourceGroup(std::string &streamId,MediaFrameType type); 1064 | 1065 | MediaFrameType type; 1066 | RTPOutgoingSource media; 1067 | RTPOutgoingSource fec; 1068 | RTPOutgoingSource rtx; 1069 | 1070 | void Update(); 1071 | }; 1072 | 1073 | 1074 | %nodefaultctor RTPSender; 1075 | %nodefaultdtor RTPSender; 1076 | struct RTPSender {}; 1077 | 1078 | %nodefaultctor RTPReceiver; 1079 | %nodefaultdtor RTPReceiver; 1080 | struct RTPReceiver {}; 1081 | 1082 | %{ 1083 | using RTPIncomingMediaStreamListener = RTPIncomingMediaStream::Listener; 1084 | %} 1085 | %nodefaultctor RTPIncomingMediaStreamListener; 1086 | struct RTPIncomingMediaStreamListener 1087 | { 1088 | 1089 | }; 1090 | 1091 | 1092 | %nodefaultctor RTPIncomingMediaStream; 1093 | %nodefaultdtor RTPIncomingMediaStream; 1094 | struct RTPIncomingMediaStream { 1095 | 1096 | }; 1097 | 1098 | 1099 | 1100 | struct RTPIncomingSourceGroup : public RTPIncomingMediaStream 1101 | { 1102 | RTPIncomingSourceGroup(MediaFrameType type, TimeService& TimeService); 1103 | std::string rid; 1104 | std::string mid; 1105 | DWORD rtt; 1106 | MediaFrameType type; 1107 | RTPIncomingSource media; 1108 | RTPIncomingSource fec; 1109 | RTPIncomingSource rtx; 1110 | 1111 | DWORD lost; 1112 | DWORD minWaitedTime; 1113 | DWORD maxWaitedTime; 1114 | double avgWaitedTime; 1115 | 1116 | void AddListener(RTPIncomingMediaStreamListener* listener); 1117 | void RemoveListener(RTPIncomingMediaStreamListener* listener); 1118 | 1119 | void Update(); 1120 | }; 1121 | 1122 | 1123 | struct RTPIncomingMediaStreamMultiplexer : public RTPIncomingMediaStreamListener,public RTPIncomingMediaStream 1124 | { 1125 | RTPIncomingMediaStreamMultiplexer(DWORD ssrc, TimeService& TimeService); 1126 | void Stop(); 1127 | }; 1128 | 1129 | 1130 | 1131 | class PropertiesFacade : private Properties 1132 | { 1133 | public: 1134 | void SetPropertyInt(const char* key,int intval); 1135 | void SetPropertyStr(const char* key,const char* val); 1136 | void SetPropertyBool(const char* key,bool boolval); 1137 | }; 1138 | 1139 | class MediaServer 1140 | { 1141 | public: 1142 | static void Initialize(); 1143 | static void EnableLog(bool flag); 1144 | static void EnableDebug(bool flag); 1145 | static void EnableUltraDebug(bool flag); 1146 | static std::string GetFingerprint(); 1147 | static bool SetPortRange(int minPort, int maxPort); 1148 | }; 1149 | 1150 | 1151 | 1152 | %nodefaultctor RTPBundleTransportConnection; 1153 | %nodefaultdtor RTPBundleTransportConnection; 1154 | struct RTPBundleTransportConnection 1155 | { 1156 | DTLSICETransport* transport; 1157 | bool disableSTUNKeepAlive; 1158 | size_t iceRequestsSent = 0; 1159 | size_t iceRequestsReceived = 0; 1160 | size_t iceResponsesSent = 0; 1161 | size_t iceResponsesReceived = 0; 1162 | }; 1163 | 1164 | 1165 | 1166 | class RTPBundleTransport 1167 | { 1168 | public: 1169 | RTPBundleTransport(); 1170 | int Init(); 1171 | int Init(int port); 1172 | RTPBundleTransportConnection* AddICETransport(const std::string &username,const Properties& properties); 1173 | int RemoveICETransport(const std::string &username); 1174 | int End(); 1175 | int GetLocalPort() const { return port; } 1176 | int AddRemoteCandidate(const std::string& username,const char* ip, WORD port); 1177 | bool SetAffinity(int cpu); 1178 | void SetIceTimeout(uint32_t timeout); 1179 | TimeService& GetTimeService(); 1180 | }; 1181 | 1182 | 1183 | 1184 | class DTLSICETransportListener 1185 | { 1186 | public: 1187 | DTLSICETransportListener(); 1188 | virtual ~DTLSICETransportListener() {}; 1189 | // swig does not support inter class 1190 | virtual void onDTLSStateChange(uint32_t state); 1191 | }; 1192 | 1193 | 1194 | %{ 1195 | using RemoteRateEstimatorListener = RemoteRateEstimator::Listener; 1196 | %} 1197 | %nodefaultctor RemoteRateEstimatorListener; 1198 | struct RemoteRateEstimatorListener 1199 | { 1200 | }; 1201 | 1202 | 1203 | %nodefaultctor DTLSICETransport; 1204 | class DTLSICETransport 1205 | { 1206 | public: 1207 | 1208 | void SetListener(DTLSICETransportListener* listener); 1209 | 1210 | void Start(); 1211 | void Stop(); 1212 | 1213 | void SetSRTPProtectionProfiles(const std::string& profiles); 1214 | void SetRemoteProperties(const Properties& properties); 1215 | void SetLocalProperties(const Properties& properties); 1216 | virtual int SendPLI(DWORD ssrc) override; 1217 | virtual int Enqueue(const RTPPacket::shared& packet) override; 1218 | int Dump(const char* filename, bool inbound = true, bool outbound = true, bool rtcp = true, bool rtpHeadersOnly = false); 1219 | int Dump(UDPDumper* dumper, bool inbound = true, bool outbound = true, bool rtcp = true, bool rtpHeadersOnly = false); 1220 | int DumpBWEStats(const char* filename); 1221 | void Reset(); 1222 | 1223 | void ActivateRemoteCandidate(ICERemoteCandidate* candidate,bool useCandidate, DWORD priority); 1224 | int SetRemoteCryptoDTLS(const char *setup,const char *hash,const char *fingerprint); 1225 | int SetLocalSTUNCredentials(const char* username, const char* pwd); 1226 | int SetRemoteSTUNCredentials(const char* username, const char* pwd); 1227 | bool AddOutgoingSourceGroup(RTPOutgoingSourceGroup *group); 1228 | bool RemoveOutgoingSourceGroup(RTPOutgoingSourceGroup *group); 1229 | bool AddIncomingSourceGroup(RTPIncomingSourceGroup *group); 1230 | bool RemoveIncomingSourceGroup(RTPIncomingSourceGroup *group); 1231 | 1232 | void SetBandwidthProbing(bool probe); 1233 | void SetMaxProbingBitrate(DWORD bitrate); 1234 | void SetProbingBitrateLimit(DWORD bitrate); 1235 | void SetSenderSideEstimatorListener(RemoteRateEstimatorListener* listener); 1236 | 1237 | const char* GetRemoteUsername() const; 1238 | const char* GetRemotePwd() const; 1239 | const char* GetLocalUsername() const; 1240 | const char* GetLocalPwd() const; 1241 | 1242 | DWORD GetRTT() const { return rtt; } 1243 | 1244 | QWORD GetLastActiveTime() { return lastActiveTime; } 1245 | 1246 | TimeService& GetTimeService(); 1247 | }; 1248 | 1249 | 1250 | 1251 | class RTPSessionFacade : 1252 | public RTPSender, 1253 | public RTPReceiver 1254 | { 1255 | public: 1256 | RTPSessionFacade(MediaFrameType media); 1257 | int Init(const Properties &properties); 1258 | int SetLocalPort(int recvPort); 1259 | int GetLocalPort(); 1260 | int SetRemotePort(char *ip,int sendPort); 1261 | RTPOutgoingSourceGroup* GetOutgoingSourceGroup(); 1262 | RTPIncomingSourceGroup* GetIncomingSourceGroup(); 1263 | int End(); 1264 | virtual int Enqueue(const RTPPacket::shared& packet); 1265 | virtual int SendPLI(DWORD ssrc); 1266 | }; 1267 | 1268 | 1269 | class RTPSenderFacade 1270 | { 1271 | public: 1272 | RTPSenderFacade(DTLSICETransport* transport); 1273 | RTPSenderFacade(RTPSessionFacade* session); 1274 | RTPSender* get(); 1275 | 1276 | }; 1277 | 1278 | class RTPReceiverFacade 1279 | { 1280 | public: 1281 | RTPReceiverFacade(DTLSICETransport* transport); 1282 | RTPReceiverFacade(RTPSessionFacade* session); 1283 | RTPReceiver* get(); 1284 | int SendPLI(DWORD ssrc); 1285 | }; 1286 | 1287 | 1288 | RTPSenderFacade* TransportToSender(DTLSICETransport* transport); 1289 | RTPReceiverFacade* TransportToReceiver(DTLSICETransport* transport); 1290 | RTPSenderFacade* SessionToSender(RTPSessionFacade* session); 1291 | RTPReceiverFacade* SessionToReceiver(RTPSessionFacade* session); 1292 | RTPReceiverFacade* RTPSessionToReceiver(MediaFrameSessionFacade* session); 1293 | 1294 | 1295 | class RTPStreamTransponderFacade 1296 | { 1297 | public: 1298 | RTPStreamTransponderFacade(RTPOutgoingSourceGroup* outgoing,RTPSenderFacade* sender); 1299 | bool SetIncoming(RTPIncomingMediaStream* incoming, RTPReceiverFacade* receiver); 1300 | bool SetIncoming(RTPIncomingMediaStream* incoming, RTPReceiver* receiver); 1301 | void SelectLayer(int spatialLayerId,int temporalLayerId); 1302 | void Mute(bool muting); 1303 | void Close(); 1304 | }; 1305 | 1306 | 1307 | %nodefaultctor MediaFrameListener; 1308 | %nodefaultdtor MediaFrameListener; 1309 | struct MediaFrameListener 1310 | { 1311 | }; 1312 | 1313 | 1314 | class StreamTrackDepacketizer 1315 | { 1316 | public: 1317 | StreamTrackDepacketizer(RTPIncomingMediaStream* incomingSource); 1318 | void AddMediaListener(MediaFrameListener* listener); 1319 | void RemoveMediaListener(MediaFrameListener* listener); 1320 | void Stop(); 1321 | }; 1322 | 1323 | 1324 | 1325 | 1326 | class MP4RecorderFacade : 1327 | public MediaFrameListener 1328 | { 1329 | public: 1330 | MP4RecorderFacade(); 1331 | 1332 | //Recorder interface 1333 | virtual bool Create(const char *filename); 1334 | virtual bool Record(); 1335 | virtual bool Record(bool waitVideo); 1336 | virtual bool Stop(); 1337 | virtual bool Close(); 1338 | void SetTimeShiftDuration(DWORD duration); 1339 | bool Close(bool async); 1340 | }; 1341 | 1342 | 1343 | class MediaFrameSessionFacade : 1344 | public RTPReceiver 1345 | { 1346 | public: 1347 | MediaFrameSessionFacade(MediaFrameType media); 1348 | int Init(const Properties &properties); 1349 | void onRTPPacket(uint8_t* buffer, int len); 1350 | void onRTPData(uint8_t* buffer, int size, uint8_t payloadType); 1351 | RTPIncomingSourceGroup* GetIncomingSourceGroup(); 1352 | int End(); 1353 | virtual int SendPLI(DWORD ssrc); 1354 | }; 1355 | 1356 | 1357 | class SenderSideEstimatorListener : 1358 | public RemoteRateEstimatorListener 1359 | { 1360 | public: 1361 | SenderSideEstimatorListener(); 1362 | virtual ~SenderSideEstimatorListener() {} 1363 | void onTargetBitrateRequested(DWORD bitrate); 1364 | }; 1365 | 1366 | 1367 | class ActiveSpeakerDetectorFacade 1368 | { 1369 | public: 1370 | ActiveSpeakerDetectorFacade(ActiveTrackListener* listener); 1371 | void SetMinChangePeriod(uint32_t minChangePeriod); 1372 | void SetMaxAccumulatedScore(uint64_t maxAcummulatedScore); 1373 | void SetNoiseGatingThreshold(uint8_t noiseGatingThreshold); 1374 | void SetMinActivationScore(uint32_t minActivationScore); 1375 | void AddIncomingSourceGroup(RTPIncomingMediaStream* incoming, uint32_t id); 1376 | void RemoveIncomingSourceGroup(RTPIncomingMediaStream* incoming); 1377 | }; 1378 | 1379 | 1380 | class MediaFrameListenerFacade 1381 | { 1382 | public: 1383 | MediaFrameListenerFacade(); 1384 | virtual ~MediaFrameListenerFacade() {} 1385 | virtual void onMediaFrame(const MediaFrame &frame); 1386 | }; 1387 | 1388 | 1389 | class MediaFrameMultiplexer 1390 | { 1391 | public: 1392 | MediaFrameMultiplexer(RTPIncomingMediaStream* incomingSource); 1393 | void AddMediaListener(MediaFrameListenerFacade* listener); 1394 | void RemoveMediaListener(MediaFrameListenerFacade* listener); 1395 | void Stop(); 1396 | }; 1397 | 1398 | 1399 | class ActiveTrackListener { 1400 | public: 1401 | ActiveTrackListener(); 1402 | virtual ~ActiveTrackListener() {} 1403 | virtual void onActiveTrackchanged(uint32_t id); 1404 | }; 1405 | 1406 | 1407 | 1408 | 1409 | --------------------------------------------------------------------------------