Minimize lag in video streaming over TCP in python - python-3.x

I am doing a research using raspberry pi. I want to stream video from my raspberry pi (Client) to my laptop (Server) for my project. After two days research i have found following code.
CLIENT
import socket
import cv2
import pickle
import struct
device = cv2.VideoCapture(-1)
ok, frame = device.read()
host = '192.168.1.4'
port = 12345
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((host, port))
while True:
ok, frame = device.read()
data = pickle.dumps(frame)
client_socket.sendall(struct.pack("L", len(data)) + data)
cv2.imshow("",frame)
if cv2.waitKey(1) == 27:
break
device.release()
cv2.destroyAllWindows()
SERVER
import socket
import cv2
import numpy as np
import time
import pickle
import struct
host = '192.168.1.4'
port = 12345
client_socket = None
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((host, port))
server_socket.listen(5)
conn, addr = server_socket.accept()
data = b''
payload_size = struct.calcsize("L")
while True:
while len(data) < payload_size:
#data += conn.recv(90456)
data += conn.recv(4096)
packed_msg_size = data[:payload_size]
data = data[payload_size:]
msg_size = struct.unpack("L", packed_msg_size)[0]
while len(data) < msg_size:
data += conn.recv(4096)
frame_data = data[:msg_size]
data = data[msg_size:]
frame = pickle.loads(frame_data)
#print (frame.size)
cv2.imshow('frame', frame)
cv2.waitKey(1)
But there is a big lag. What i want is live streaming (if possible) or as much as close to live streaming (with minimum lag). Someone could suggest UDP but since it not guarantee that getting all frames.
Can some one suggest me to improve my code. Any help??
Thank you very much in advance!!

Related

Receiving a full image from a TCP socket communication

I have a TCP communication where my client continuously sends images as a byte array to a server and receives a response back, my problem is that when the server receives the images they are not done being received even though I've added a flag to indicate the end of the image.
I'd like to know a better way to ensure that the image file is received completely before receiving a new one
EDIT: My new attempt:
Client.py
import numpy as np
import cv2
from PIL import Image
import base64
import socket
def main(data):
s = socket.socket()
s.connect(("127.0.0.1", 999))
decoded_data = base64.b64decode(data)
print("Sending...")
s.sendall(decoded_data)
s.shutdown(s.SHUTWR)
b_data = b''
while True:
txt_data = s.recv(2048)
if not txt_data: break
b_data += txt_data
print('response received from the server: ' + b_data.decode())
return b_data.decode()
Server.py
import socket
from PIL import Image
import io
import numpy as np
import cv2
import uuid
IP = '127.0.0.1'
PORT = 999
with socket.socket() as s:
s.bind((IP,PORT))
s.listen(1)
count = 0
print ('The server is ready')
while True:
con, addr = s.accept()
filename = str(uuid.uuid4())
count = count + 1
img_dir = 'C:/Users/my_user/stream_images/'
img_format = '.png'
with con:
img = b''
while True:
data = con.recv(2048)
if not data:
break
img += data
image_name = img_dir+'frame'+str(count)+img_format
pil_image = io.BytesIO(img)
img = np.array(Image.open(pil_image))
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
cCode = str('Thank you for connecting')
con.sendall(cCode.encode())
print("called con.sendall")
cv2.imshow('frame', img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
Currently, I am now able to fully send the images and receive them properly at the server, the only problem is that I am no longer sending a response back after the image is received, so there is something wrong with how I am receiving the reply message at the client side.
As user207421 suggested you can shutdown the socket for writing after sending the image on the client-side while still being able to receive an confirmatory answer from the server. Another problem you're facing here is the blocking nature of cv2.waitKey, which essentially halts the server until the user presses q in the cv2 window (the server will not be able to handle any other requests). I'd generally recommend to separate your network/IO logic from user interface logic. To circumvent the blocking behaviour of I've implemented a very basic image_viewer, which waits for incoming images in a thread that runs separately from the server loop by passing images through a Queue.
The client code looks as follows:
import socket
from PIL import Image
def send_image(img: Image, host: str = '127.0.0.1', port: int = 999):
s = socket.socket()
s.connect((host, port))
img_data = img._repr_png_()
s.sendall(img_data)
s.shutdown(socket.SHUT_WR) # close socket for writing, receiving is still possible
print(f'Sent {len(img_data) / 1024:,.1f} kB of image data.')
b_data = b''
while recv_data := s.recv(2048):
b_data += recv_data
print(f'Server response: {b_data.decode()}')
# maybe check server response for server side errors etc. and add return value for this function?
# use like: send_image(Image.open('test.png'))
The server code is:
import io
import queue
import socket
import threading
import cv2
import numpy as np
from PIL import Image
def image_viewer(q: queue.Queue):
while True:
try:
img_name, img = q.get(block=True, timeout=.1) # poll every 0.1 seconds
print(f'Image viewer: displaying `{img_name}`!')
cv2.imshow('Image preview', img)
except queue.Empty:
... # no new image to display
key = cv2.pollKey() # non-blocking
if key & 0xff == ord('q'):
cv2.destroyAllWindows()
print('Image viewer was closed')
return
def serve_forever(host: str, port: int, img_dir: str = 'C:/Users/my_user/stream_images/', img_format: str = '.png'):
q = queue.Queue()
img_viewer = threading.Thread(target=image_viewer, args=(q,))
img_viewer.start()
with socket.socket() as s:
s.bind((host, port))
s.listen(1)
count = 0
print('The server is ready')
while True:
conn, addr = s.accept()
count = count + 1
img_name = img_dir + 'frame' + str(count) + img_format
print (f'Client connected: {addr}')
img = b''
while data := conn.recv(2048):
img += data
conn.sendall('Thank you for connecting'.encode()) # maybe use return codes for success, error etc.?
conn.close()
pil_img = Image.open(io.BytesIO(img)) # might want to save to disk?
np_img = np.asarray(pil_img)
np_img = cv2.rotate(np_img, cv2.ROTATE_90_CLOCKWISE)
q.put((img_name, np_img))
print (f'Client at {addr} disconnected after receiving {len(img) / 1024:,.1f} kB of data.')
if __name__ == '__main__':
serve_forever('127.0.0.1', 999)

How can i stream my screen over another virtual machine in cloud using python?

I am trying to capture gameplay over my computer and wants the live captured gameplay to be sent to my cloud instance for the purpose of object detection.
I have tried the following code.It works well locally but when I tunnel it using ngrok I am getting
getaddress error.
Is there any other way which I can send the video frames at good FPS over the internet to my cloud instance for performing deep learning processing on it?
client.py
import cv2
import io
import socket
import struct
import time
import pickle
import zlib
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#client_socket.connect(('localhost', 8485))
#Tring to connect to the hosted address obtained after using ngrok http 8485 command.
client_socket.connect(('https://fc766cc3.ngrok.io',0))
connection = client_socket.makefile('wb')
cam = cv2.VideoCapture(0)
cam.set(3, 320);
cam.set(4, 240);
img_counter = 0
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90]
while True:
ret, frame = cam.read()
result, frame = cv2.imencode('.jpg', frame, encode_param)
# data = zlib.compress(pickle.dumps(frame, 0))
data = pickle.dumps(frame, 0)
size = len(data)
print("{}: {}".format(img_counter, size))
client_socket.sendall(struct.pack(">L", size) + data)
img_counter += 1
cam.release()
server.py
import socket
import sys
import cv2
import pickle
import numpy as np
import struct ## new
import zlib
HOST='localhost'
PORT=8485
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print('Socket created')
s.bind((HOST,PORT))
print('Socket bind complete')
s.listen(10)
print('Socket now listening')
conn,addr=s.accept()
data = b""
payload_size = struct.calcsize(">L")
print("payload_size: {}".format(payload_size))
while True:
while len(data) < payload_size:
print("Recv: {}".format(len(data)))
data += conn.recv(4096)
print("Done Recv: {}".format(len(data)))
packed_msg_size = data[:payload_size]
data = data[payload_size:]
msg_size = struct.unpack(">L", packed_msg_size)[0]
print("msg_size: {}".format(msg_size))
while len(data) < msg_size:
data += conn.recv(4096)
frame_data = data[:msg_size]
data = data[msg_size:]
frame=pickle.loads(frame_data, fix_imports=True, encoding="bytes")
frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
cv2.imshow('ImageWindow',frame)
cv2.waitKey(1)
FIRST OF ALL
You have generated the wrong Link for the client to connect to i.e
#Tring to connect to the hosted address obtained after using ngrok http 8485 command.
client_socket.connect(('https://fc766cc3.ngrok.io',0))
You neeed to use TCP instead of HTTP when using NGROK . Because the Http is used when you are hosting a web app on your server . so the client can go over to any browser paste your provided link and port and connect to your website.
And you used port 0 , I maybe wrong but i dont think portzr 0 is useable
and NGROK does not provide you port 0.
AND SECOND :
Even if you do get a TCP link NGROK does not allow Screen sharing over their Free server . You are able to send small messages or files but Screen Sharing is not possible .

How do I shutdown a socket properly that is streaming webcam footage?

I am trying to stream my laptops webcam stream to my pc for cv2 manipulation. The stream works but I am not really sure how to go about closing the socket.
I don't have a webcam on my desktop so I wanted to try and stream my laptops webcam over to learn cv2 on the footage. I am new to sockets, I went through the documentation and did a bunch of googling to get this far, even though most of what I now have was copy pasted. I am not really sure where to look to learn how to shut down sockets propery when streaming video data with cv2.
I've tried using with socket.socket( ... ) as s: to close the connection
breaks after the while and try loops, that go into shutdown(socket.SHUT_WR and close() and I've tried terminating the program on both the client and server.
#server.py
import socket
import cv2
import pickle
import struct
def main():
HOST=''
PORT=12397
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
print('Socket created')
s.bind((HOST,PORT))
print('Socket bind complete')
s.listen(10)
print('Socket now listening')
conn,addr=s.accept()
data = b""
payload_size = struct.calcsize(">L")
while True:
try:
key = cv2.waitKey(20) & 0xFF
while len(data) < payload_size:
data += conn.recv(4096)
if key == ord("q"):
print("Socket closed.")
break
packed_msg_size = data[:payload_size]
data = data[payload_size:]
msg_size = struct.unpack(">L", packed_msg_size)[0]
while len(data) < msg_size:
data += conn.recv(4096)
if key == ord("q"):
print("Socket closed.")
break
frame_data = data[:msg_size]
data = data[msg_size:]
frame=pickle.loads(frame_data, fix_imports=True, encoding="bytes")
frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
cv2.imshow('ImageWindow',frame)
cv2.waitKey(1)
if key == ord("q"):
print("Socket closed.")
break
except:
cv2.destroyAllWindows()
break
s.shutdown(socket.SHUT_WR)
s.close()
if __name__ == "__main__":
main()
#client.py
import socket
import cv2
import pickle
import struct
def main():
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90]
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as client_socket:
print('Connecting to socket.')
client_socket.connect((ENTER IP HERE, 12397))
connection = client_socket.makefile('wb')
print('Starting stream.')
cam = cv2.VideoCapture(0)
cam.set(3, 320*2);
cam.set(4, 240*2);
frame_time = int((1.0 / 30.0) * 1000.0)
img_counter = 0
while True:
try:
ret, frame = cam.read()
if ret:
result, frame = cv2.imencode('.jpg', frame, encode_param)
data = pickle.dumps(frame, 0)
size = len(data)
client_socket.sendall(struct.pack(">L", size) + data)
img_counter += 1
if cv2.waitKey(frame_time) & 0xFF == ord('q'):
break
else:
break
except KeyboardInterrupt:
break
cam.release()
client_socket.shutdown(socket.SHUT_WR)
client_socket.close()
if __name__ == "__main__":
main()
I want to close the connection by pressing q.
I currently get WinErrors on the server
OSError: [WinError 10038] An operation was attempted on something that is not a socket
and on the client
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host
when I (spam) "q" on the server side, it doesn't seem to catch if I just press it once.
That error typically happen if you use the socket after you have closed it.
Now if we take your server, you do something like
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
... code here ...
s.shutdown(socket.SHUT_WR)
s.close()
That means the shutdown and close calls are done after the socket have already been closed.
You have the same problem in the client program.

Sending a .mp4 file over sockets in python3

I am trying to make two small programs; one is a server which will receive mp4 files from a client. The client is just a small program that sends a .mp4 file located in its folder.
I am able to fully send the mp4 file and a file in the same size is created, but for some reason the mp4 gets corrupted or something else goes wrong and I am unable to play the mp4 file in QuickTime player or VLC.
I don't understand this as I am copying all bytes and sending then all in small packets. I would really appreciate some help or some tips.
Server code:
#!/usr/bin/python3
from socket import socket, gethostname
s = socket()
host = gethostname()
port = 3399
s.bind((host, port))
s.listen(5)
n = 0
while True:
print("Listening for connections...")
connection, addr = s.accept()
try:
print("Starting to read bytes..")
buffer = connection.recv(1024)
with open('video_'+str(n), "wb") as video:
n += 1
i = 0
while buffer:
buffer = connection.recv(1024)
video.write(buffer)
print("buffer {0}".format(i))
i += 1
print("Done reading bytes..")
connection.close()
except KeyboardInterrupt:
if connection:
connection.close()
break
s.close()
Client code:
#!/usr/bin/python3
from socket import socket, gethostname, SHUT_WR
s = socket()
host = gethostname()
port = 3399
s.connect((host, port))
print("Sending video..")
with open("test.mp4", "rb") as video:
buffer = video.read()
print(buffer)
s.sendall(buffer)
print("Done sending..")
s.close()
Fix bugs in your server code:
#!/usr/bin/python3
from socket import socket, gethostname
s = socket()
host = gethostname()
port = 3399
s.bind((host, port))
s.listen(5)
n = 0
while True:
print("Listening for connections...")
connection, addr = s.accept()
try:
print("Starting to read bytes..")
buffer = connection.recv(1024)
with open('video_'+str(n)+'.mp4', "wb") as video:
n += 1
i = 0
while buffer:
video.write(buffer)
print("buffer {0}".format(i))
i += 1
buffer = connection.recv(1024)
print("Done reading bytes..")
connection.close()
except KeyboardInterrupt:
if connection:
connection.close()
break
s.close()
fix here:
with open('video_'+str(n)+'.mp4', "wb") as video:
and here:
while buffer:
video.write(buffer)
print("buffer {0}".format(i))
i += 1
buffer = connection.recv(1024)

How the client can get the server uploaded original file of filename and extension?

Please help me I just a beginner of python and I want to learn this. I have no idea how to get the original filename and extension from the server part.
I try many ways and research but still cannot work it. I have seen many types of example those just can only upload text file with with open('received_file','.txt','wb') as f: in the client part and cannot upload multiple type extension of files. I know because of the '.txt' so just work for text file. I don't how to declare to get multiple extension and original filename. This is my original code.
client
import socket
import os
TCP_IP = 'localhost'
TCP_PORT = 9001
BUFFER_SIZE = 8192
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
#data = s.recv(BUFFER_SIZE)
with open('received_file','.txt','wb') as f:
print ('file opened')
while True:
print('receiving data...')
data = s.recv(BUFFER_SIZE)
print('data=%s', (data))
if not data:
f.close()
print ('file close()')
break
# write data to a file
f.write(data)
print('Successfully get the file')
s.close()
print('connection closed')
Blockquote
server
import socket
from threading import Thread
from socketserver import ThreadingMixIn
import tkinter
import tkinter.filedialog
TCP_IP = 'localhost'
TCP_PORT = 9001
BUFFER_SIZE = 8192
tkinter.Tk().withdraw()
in_path = tkinter.filedialog.askopenfilename( )
class ClientThread(Thread):
def __init__(self,ip,port,sock):
Thread.__init__(self)
self.ip = ip
self.port = port
self.sock = sock
print (" New thread started for "+ip+":"+str(port))
def run(self):
filename= in_path
f = open(filename,'rb')
while True:
l = f.read(BUFFER_SIZE)
while (l):
self.sock.send(l)
l = f.read(BUFFER_SIZE)
if not l:
f.close()
self.sock.close()
break
tcpsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcpsock.bind((TCP_IP, TCP_PORT))
threads = []
while True:
tcpsock.listen(5)
print ("Waiting for incoming connections...")
(conn, (ip,port)) = tcpsock.accept()
print ('Got connection from ', (ip,port))
newthread = ClientThread(ip,port,conn)
newthread.start()
threads.append(newthread)
for t in threads:
t.join()
The output file of name is received_file without the extension.
You need to define a protocol that transmits the filename, then the data. Here's an example that transmits the content of input.txt to the client as the filename output.txt. The client reads the filename, and then writes the data to that filename. I did this simply because the client and server ran on the same computer, and read/write files in the same directory.
server.py
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
filename = 'output.txt'
self.request.sendall(filename.encode() + b'\r\n')
with open('input.txt','rb') as f:
self.request.sendall(f.read())
if __name__ == "__main__":
HOST, PORT = "localhost", 9001
with socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) as server:
server.serve_forever()
client.py
import socket
import os
SERVER = 'localhost',9001
s = socket.socket()
s.connect(SERVER)
# autoclose f and s when with block is exited.
# makefile treats the socket as a file stream.
# Open in binary mode so the bytes of the file are received as is.
with s,s.makefile('rb') as f:
# The first line is the UTF-8-encoded filename. Strip the line delimiters.
filename = f.readline().rstrip(b'\r\n').decode()
with open(filename,'wb') as out:
out.write(f.read())

Resources