├── .gitignore ├── README.md ├── __pycache__ └── utils.cpython-36.pyc ├── client-mpquic.go ├── client_mptcp.py ├── config └── config.go ├── configure.py ├── sample.txt ├── server-mpquic.go ├── server_mptcp.py ├── utils.py └── utils └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | received_file 2 | __pycache__/ 3 | files_received/ 4 | server-mpquic 5 | client-mpquic 6 | test 7 | test.go 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Video Streaming based on MP-TCP and MP-QUIC 2 | Network Programming Project 3 | It allows you to stream your your webcam video from one system to the other. The protocols used for streaming are MP-TCP and MP-QUIC. 4 | You can separately try out MP-TCP or MP-QUIC while using this code. Please check the requirement below for running MP-TCP or MP-QUIC. 5 | 6 | ## Requirements 7 | ### MP-TCP 8 | - MP-TCP Kernel (Not supported on all OS): 9 | For Linux based OS, please refer to the following [link](https://multipath-tcp.org/pmwiki.php/Users/AptRepository/ "link") for installing MP-TCP protocol on your kernel. 10 | 11 | - Python (Python 3.5 recommended) 12 | 13 | - Python libraries - **numpy** **opencv-python** 14 | ``` 15 | python3 -m pip install numpy, opencv-python 16 | ``` 17 | 18 | ### MP-QUIC 19 | - GO Language 20 | - [quic](https://github.com/lucas-clemente/quic-go "quic Library") 21 | - [mp-quic](https://github.com/qdeconinck/mp-quic "mpquic Library") 22 | - GOCV - [link](https://gocv.io/ "link") - Refer to the installation instructions for GoCV 23 | 24 | ## Installation 25 | - Clone this repository in your preferred directory 26 | 27 | ``` 28 | git clone https://github.com/prat-bphc52/VideoStreaming-MPTCP-MPQUIC 29 | ``` 30 | - Or you can also download the source code as a zip file 31 | 32 | ## Execution 33 | ### MP-TCP 34 | Start the server on the Video Streaming Source Host 35 | 36 | ``` 37 | python3 server_mptcp.py localhost -p 38 | ``` 39 | 40 | Start the client on the target machine 41 | ``` 42 | python3 client_mptcp.py -p 43 | ``` 44 | 45 | ### MP-QUIC 46 | Build and execute server on one host 47 | ``` 48 | go build server-mpquic.go 49 | ./server-mpquic 50 | ``` 51 | Specify the server's host name in client-mpquic.go 52 | Build and execute client on the other host 53 | ``` 54 | go build client-mpquic.go 55 | ./client-mpquic 56 | ``` 57 | 58 | ## Team Members 59 | - [Prateek Agarwal](https://github.com/prat-bphc52/ "Prateek Agarwal") - 2017A7PS0075H 60 | - [Naman Arora](https://github.com/namanarora00/ "Naman Arora") - 2017A7PS0175H 61 | - [Utkarsh Grover](https://github.com/utkgrover/ "Utkarsh Grover") - 2017A7PS1428H 62 | - Samar Kansal - 2016AAPS0196H 63 | -------------------------------------------------------------------------------- /__pycache__/utils.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prat-bphc52/VideoStreaming-MPTCP-MPQUIC/78674a518bf13c2f155f797959a40d9f7c49d29f/__pycache__/utils.cpython-36.pyc -------------------------------------------------------------------------------- /client-mpquic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "time" 7 | "encoding/binary" 8 | 9 | utils "./utils" 10 | config "./config" 11 | quic "github.com/lucas-clemente/quic-go" 12 | "gocv.io/x/gocv" 13 | ) 14 | 15 | const addr = "10.0.0.2:"+config.PORT 16 | var pl = fmt.Println 17 | var p = fmt.Print 18 | 19 | func main() { 20 | 21 | quicConfig := &quic.Config{ 22 | CreatePaths: true, 23 | } 24 | 25 | 26 | pl("Trying to connect to: ", addr) 27 | sess, err := quic.DialAddr(addr, &tls.Config{InsecureSkipVerify: true}, quicConfig) 28 | utils.HandleError(err) 29 | 30 | stream, err := sess.OpenStream() 31 | utils.HandleError(err) 32 | 33 | pl("Connection established with server successfully...Starting Video stream") 34 | defer stream.Close() 35 | 36 | webcam, _ := gocv.VideoCaptureDevice(0) 37 | img := gocv.NewMat() 38 | 39 | start := time.Now() 40 | 41 | webcam.Read(&img) 42 | 43 | pl("Video Dimensions : ", img.Rows(), " x ", img.Cols()) 44 | var dimens = make([]byte, 4) 45 | var bs = make([]byte, 2) 46 | binary.LittleEndian.PutUint16(bs, uint16(img.Rows())) 47 | copy(dimens[0:],bs) 48 | binary.LittleEndian.PutUint16(bs, uint16(img.Cols())) 49 | copy(dimens[2:],bs) 50 | stream.Write(dimens) 51 | 52 | var count = 1 53 | for ;count<=config.MAX_FRAMES;count++{ 54 | webcam.Read(&img) 55 | var b = img.ToBytes() 56 | for ind:=0;indlen(b){ 59 | end = len(b) 60 | } 61 | stream.Write(b[ind:end]) 62 | ind = end 63 | } 64 | 65 | } 66 | stream.Write([]byte{0,0,0,0}) 67 | webcam.Close() 68 | 69 | elapsed := time.Since(start) 70 | pl("\nEnding Video Stream, Duration : ", elapsed, " Frames Sent ", count) 71 | 72 | stream.Close() 73 | stream.Close() 74 | pl("\n\nThank you!") 75 | } 76 | -------------------------------------------------------------------------------- /client_mptcp.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import socket 3 | import utils 4 | import numpy as np 5 | import cv2 6 | from datetime import datetime 7 | 8 | MAX_BYTES = 100000 9 | 10 | class DataExceededError(Exception): 11 | pass 12 | 13 | def startClient(host, port): 14 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 15 | s.connect((host, port)) 16 | 17 | starttime = datetime.now() 18 | print('Starting Video Streaming at ', starttime) 19 | 20 | count = 0 21 | frame = None 22 | frameOld = None 23 | rows1, rows2 = -1, -1 24 | cols1, cols2 = -1, -1 25 | rows, cols = 0, 0 26 | pos_i, pos_j, pos_k = 0, 0, 0 27 | while True: 28 | try: 29 | data_pos = 0 30 | data = s.recv(MAX_BYTES) 31 | if not data: 32 | break 33 | while data_pos=len(data): 39 | raise DataExceededError 40 | if rows2==-1: 41 | rows2= data[data_pos] 42 | data_pos = data_pos+1 43 | rows = (rows1 << 8) + rows2 44 | if data_pos>=len(data): 45 | raise DataExceededError 46 | if cols1==-1: 47 | cols1 = data[data_pos] 48 | data_pos = data_pos+1 49 | if data_pos>=len(data): 50 | raise DataExceededError 51 | if cols2==-1: 52 | cols2= data[data_pos] 53 | data_pos = data_pos+1 54 | cols = (cols1 << 8) + cols2 55 | frame = np.zeros((rows, cols, 3), np.uint8) 56 | if data_pos>=len(data): 57 | raise DataExceededError 58 | frame[pos_i][pos_j][pos_k] = data[data_pos] 59 | data_pos = data_pos + 1 60 | pos_k = pos_k+1 61 | if pos_k==3: 62 | pos_k = 0 63 | pos_j = pos_j + 1 64 | if pos_j == cols: 65 | pos_j = 0 66 | pos_i = pos_i + 1 67 | if pos_i == rows: 68 | pos_i = 0 69 | frameOld = frame 70 | count = count + 1 71 | frame = None 72 | rows1 = -1 73 | rows2 = -1 74 | cols1 = -1 75 | cols2 = -1 76 | rows = 0 77 | cols = 0 78 | cv2.imshow('Output', frameOld) 79 | cv2.waitKey(1) 80 | except DataExceededError: 81 | pass 82 | endtime = datetime.now() 83 | print('Ending video streaming at ', endtime) 84 | print('Frame Received ', count) 85 | cv2.destroyAllWindows() 86 | s.close() 87 | print('connection closed') 88 | 89 | if __name__ == '__main__': 90 | parser = argparse.ArgumentParser(description='Send and receive over MP-TCP') 91 | parser.add_argument('host', help='interface the server listens at;' 92 | ' host the client sends to') 93 | parser.add_argument('-p', metavar='PORT', type=int, default=6000, 94 | help='TCP port (default 6000)') 95 | args = parser.parse_args() 96 | startClient(args.host, args.p) -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // BUFFERSIZE is the size of max packet size 4 | const BUFFER_SIZE = 1024 5 | 6 | const MAX_FRAMES = 50 7 | 8 | // PORT the default port for communication 9 | const PORT = "6000" 10 | -------------------------------------------------------------------------------- /configure.py: -------------------------------------------------------------------------------- 1 | from mininet.net import Mininet 2 | from mininet.cli import CLI 3 | from mininet.link import Link, TCLink,Intf 4 | from subprocess import Popen, PIPE 5 | from mininet.log import setLogLevel 6 | 7 | 8 | if '__main__' == __name__: 9 | setLogLevel('info') 10 | net = Mininet(link=TCLink) 11 | key = "net.mptcp.mptcp_enabled" 12 | value = 1 13 | p = Popen("sysctl -w %s=%s" % (key, value), shell=True, stdout=PIPE, stderr=PIPE) 14 | stdout, stderr = p.communicate() 15 | print ("stdout=",stdout,"stderr=", stderr) 16 | h1 = net.addHost('h1') 17 | h2 = net.addHost('h2') 18 | r1 = net.addHost('r1') 19 | linkopt={'bw':10} 20 | net.addLink(r1,h1,cls=TCLink, **linkopt) 21 | net.addLink(r1,h1,cls=TCLink, **linkopt) 22 | net.addLink(r1,h2,cls=TCLink, **linkopt) 23 | net.addLink(r1,h2,cls=TCLink, **linkopt) 24 | net.build() 25 | r1.cmd("ifconfig r1-eth0 0") 26 | r1.cmd("ifconfig r1-eth1 0") 27 | r1.cmd("ifconfig r1-eth2 0") 28 | r1.cmd("ifconfig r1-eth3 0") 29 | h1.cmd("ifconfig h1-eth0 0") 30 | h1.cmd("ifconfig h1-eth1 0") 31 | h2.cmd("ifconfig h2-eth0 0") 32 | h2.cmd("ifconfig h2-eth1 0") 33 | r1.cmd("echo 1 > /proc/sys/net/ipv4/ip_forward") 34 | r1.cmd("ifconfig r1-eth0 10.0.0.1 netmask 255.255.255.0") 35 | r1.cmd("ifconfig r1-eth1 10.0.1.1 netmask 255.255.255.0") 36 | r1.cmd("ifconfig r1-eth2 10.0.2.1 netmask 255.255.255.0") 37 | r1.cmd("ifconfig r1-eth3 10.0.3.1 netmask 255.255.255.0") 38 | h1.cmd("ifconfig h1-eth0 10.0.0.2 netmask 255.255.255.0") 39 | h1.cmd("ifconfig h1-eth1 10.0.1.2 netmask 255.255.255.0") 40 | h2.cmd("ifconfig h2-eth0 10.0.2.2 netmask 255.255.255.0") 41 | h2.cmd("ifconfig h2-eth1 10.0.3.2 netmask 255.255.255.0") 42 | h1.cmd("ip rule add from 10.0.0.2 table 1") 43 | h1.cmd("ip rule add from 10.0.1.2 table 2") 44 | h1.cmd("ip route add 10.0.0.0/24 dev h1-eth0 scope link table 1") 45 | h1.cmd("ip route add default via 10.0.0.1 dev h1-eth0 table 1") 46 | h1.cmd("ip route add 10.0.1.0/24 dev h1-eth1 scope link table 2") 47 | h1.cmd("ip route add default via 10.0.1.1 dev h1-eth1 table 2") 48 | h1.cmd("ip route add default scope global nexthop via 10.0.0.1 dev h1-eth0") 49 | h2.cmd("ip rule add from 10.0.2.2 table 1") 50 | h2.cmd("ip rule add from 10.0.3.2 table 2") 51 | h2.cmd("ip route add 10.0.2.0/24 dev h2-eth0 scope link table 1") 52 | h2.cmd("ip route add default via 10.0.2.1 dev h2-eth0 table 1") 53 | h2.cmd("ip route add 10.0.3.0/24 dev h2-eth1 scope link table 2") 54 | h2.cmd("ip route add default via 10.0.3.1 dev h2-eth1 table 2") 55 | h2.cmd("ip route add default scope global nexthop via 10.0.2.1 dev h2-eth0") 56 | # h1.cmd("python3 ~/Documents/np/code.py receiver localhost") 57 | # h2.cmd("python3 ~/Documents/np/code.py sender localhost") 58 | CLI(net) 59 | net.stop() 60 | -------------------------------------------------------------------------------- /sample.txt: -------------------------------------------------------------------------------- 1 | Hello this is a sample file. -------------------------------------------------------------------------------- /server-mpquic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | utils "./utils" 8 | config "./config" 9 | quic "github.com/lucas-clemente/quic-go" 10 | "gocv.io/x/gocv" 11 | ) 12 | 13 | const addr = "0.0.0.0:" + config.PORT 14 | var pl = fmt.Println 15 | var p = fmt.Print 16 | 17 | func main() { 18 | 19 | quicConfig := &quic.Config{ 20 | CreatePaths: true, 21 | } 22 | 23 | pl("Attaching to: ", addr) 24 | listener, err := quic.ListenAddr(addr, utils.GenerateTLSConfig(), quicConfig) 25 | utils.HandleError(err) 26 | 27 | pl("Server listening...") 28 | 29 | sess, err := listener.Accept() 30 | utils.HandleError(err) 31 | stream, err := sess.AcceptStream() 32 | utils.HandleError(err) 33 | 34 | pl("Broadcasting incoming video stream...") 35 | defer stream.Close() 36 | 37 | time.Sleep(10*time.Millisecond) 38 | start := time.Now() 39 | 40 | buffer := make([]byte, config.BUFFER_SIZE) 41 | 42 | 43 | // var frame gocv.Mat 44 | var rows = -1 45 | var cols = -1 46 | 47 | window := gocv.NewWindow("Output") 48 | 49 | var dimens = make([]byte, 4) 50 | stream.Read(dimens) 51 | rows = int(dimens[1]) << 8 + int(dimens[0]) 52 | cols = int(dimens[3]) << 8 + int(dimens[2]) 53 | pl("Video Dimensions : ", rows, " x ", cols) 54 | var data = make([]byte, 3*rows*cols) 55 | var dataind = 0 56 | 57 | var count = 0 58 | for ;count= len(data){ 61 | limit = len(data)-dataind 62 | var temp = make([]byte, limit) 63 | stream.Read(temp) 64 | copy(data[dataind:],temp) 65 | count++ 66 | dataind = 0 67 | img,err := gocv.NewMatFromBytes(rows,cols,gocv.MatTypeCV8UC3,data) 68 | utils.HandleError(err) 69 | window.IMShow(img) 70 | window.WaitKey(1) 71 | } else{ 72 | stream.Read(buffer) 73 | copy(data[dataind:],buffer) 74 | dataind = dataind+limit 75 | } 76 | } 77 | 78 | elapsed := time.Since(start) 79 | pl("\nEnding video transmission, Duration: ", elapsed, " Frames Captured ", count) 80 | stream.Close() 81 | stream.Close() 82 | pl("\n\nThank you!") 83 | } 84 | -------------------------------------------------------------------------------- /server_mptcp.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import socket 3 | import cv2 4 | import utils 5 | from datetime import datetime 6 | 7 | def startServer(host,port): 8 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create a socket object 9 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 10 | s.bind(('0.0.0.0', port)) # Bind to the port 11 | s.listen() # Now wait for client connection. 12 | 13 | print('Server listening....') 14 | 15 | conn, addr = s.accept() # Establish connection with client. 16 | print('Got connection from', addr) 17 | 18 | cap = cv2.VideoCapture(0) 19 | if not cap.isOpened(): 20 | conn.close() 21 | raise IOError("Cannot open webcam") 22 | 23 | starttime = datetime.now() 24 | print('Starting Video Streaming at ', starttime) 25 | count = 0 26 | while (True): 27 | ret, frame = cap.read() 28 | frame = cv2.resize(frame, None, fx= 0.5, fy = 0.5, interpolation = cv2.INTER_AREA) 29 | conn.sendall(utils.encodeNumPyArray(frame)) 30 | count = count + 1 31 | if count==300: 32 | break 33 | cap.release() 34 | cv2.destroyAllWindows() 35 | endtime = datetime.now() 36 | print('Ending video streaming at ', endtime) 37 | print('Frames Sent ', count) 38 | conn.close() 39 | 40 | if __name__ == '__main__': 41 | parser = argparse.ArgumentParser(description='Send and receive over MP-TCP') 42 | parser.add_argument('host', help='interface the server listens at;' 43 | ' host the client sends to') 44 | parser.add_argument('-p', metavar='PORT', type=int, default=6000, 45 | help='TCP port (default 6000)') 46 | args = parser.parse_args() 47 | startServer(args.host, args.p) 48 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def encodeNumPyArray(fr): 4 | nor = len(fr).to_bytes(2,'big') # number of rows 5 | noc = len(fr[0]).to_bytes(2, 'big') # number of cols 6 | temp = np.array([nor[0],nor[1],noc[0],noc[1]], np.uint8) 7 | 8 | return np.concatenate((temp, fr), axis=None).tobytes() 9 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "fmt" 10 | "math/big" 11 | "os" 12 | ) 13 | 14 | func FillString(retunString string, toLength int) string { 15 | for { 16 | lengtString := len(retunString) 17 | if lengtString < toLength { 18 | retunString = retunString + ":" 19 | continue 20 | } 21 | break 22 | } 23 | return retunString 24 | } 25 | 26 | func GenerateTLSConfig() *tls.Config { 27 | key, err := rsa.GenerateKey(rand.Reader, 1024) 28 | if err != nil { 29 | panic(err) 30 | } 31 | template := x509.Certificate{SerialNumber: big.NewInt(1)} 32 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) 33 | if err != nil { 34 | panic(err) 35 | } 36 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) 37 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 38 | 39 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 40 | if err != nil { 41 | panic(err) 42 | } 43 | return &tls.Config{Certificates: []tls.Certificate{tlsCert}} 44 | } 45 | 46 | func HandleError(err error) { 47 | if err != nil { 48 | fmt.Println("Error: ", err) 49 | os.Exit(1) 50 | } 51 | } 52 | --------------------------------------------------------------------------------