Adnan Quaium
Adnan Quaium

Reputation: 113

How to stream and save web camera video via a server?

My scenario is as follows.

I have three computers A, B, C. Device A has a webcam. I want to stream and save the video from A to device B. At the same time, I want to watch the live video on C (from B).

I successfully managed to do the first part (saving video from A to B). But could not compete with the later part (watching live video on C).

Following are the codes that I used to build the client (on A) and server (on B) respectively.

# Client part - Device A


import cv2
import numpy as np
import socket
import sys
import pickle
import struct

cap=cv2.VideoCapture(0)
clientsocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
clientsocket.connect(('your_real_IP_address',8089))

while True:
    ret,frame=cap.read()
    data = pickle.dumps(frame)

    message_size = struct.pack("L", len(data))

    clientsocket.sendall(message_size + data)
# Server part - Device B

import pickle
import socket
import struct
import cv2

HOST = ''
PORT = 8089

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("Q") 

result = cv2.VideoWriter('output.avi', cv2.VideoWriter_fourcc(*'MJPG'), 5, (640, 480))
while True:


    while len(data) < payload_size:
        data += conn.recv(8192)  

    packed_msg_size = data[:payload_size]
    data = data[payload_size:]
    msg_size = struct.unpack("Q", packed_msg_size)[0] 

    while len(data) < msg_size:
        data += conn.recv(8192) 

    frame_data = data[:msg_size]
    data = data[msg_size:]

    frame = pickle.loads(frame_data)
    


    result.write(frame)
  
    if cv2.waitKey(1) & 0xFF == ord('s'):
        break
    

cv2.destroyAllWindows()
   
print("The video si successfully saved")

For the later part, I tried to run the following code on device C to fetch the live video from B (while it is getting saved). But I did not succeed. Can anyone please help me to fix this problem?

import socket
import pickle 
import struct
import cv2


client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host_ip = 'you_real_IP_address'  # paste your server ip address here
port = 8090
client_socket.connect((host_ip, port)) 
data = b""
payload_size = struct.calcsize("L") 

while True:
    while len(data) < payload_size:
        packet = client_socket.recv(8192)  
        if not packet: break
        data += packet # append the data packet got from server into data variable
    packed_msg_size = data[:payload_size] #will find the packed message size i.e. 8 byte, we packed on server side.
    data = data[payload_size:] # Actual frame data
    msg_size = struct.unpack("Q", packed_msg_size)[0] # meassage size
    # print(msg_size)

    while len(data) < msg_size:
        data += client_socket.recv(8192) # will receive all frame data from client socket
    frame_data = data[:msg_size] #recover actual frame data
    data = data[msg_size:]
    frame = pickle.loads(frame_data) # de-serialize bytes into actual frame type
    cv2.imshow("RECEIVING VIDEO", frame) # show video frame at client side
    key = cv2.waitKey(1) & 0xFF
    if key == ord('q'): # press q to exit video
        break

client_socket.close()

Upvotes: 1

Views: 2925

Answers (1)

SeB
SeB

Reputation: 1616

You may use gstreamer for streaming. This answer assumes that you have gstreamer installed on A, B and C and opencv built with gstreamer support as well (at least on B).

Also note that streaming in raw mode may not be the most efficient way, but the following follows your description.

For A, the hard part is that you didn't give any details about your camera. Assuming it has a native mode of 640x480@30 fps, A code could be:

#!/usr/bin/env python

import time
import cv2

while True:
    
    while True:
       cap = cv2.VideoCapture("v4l2src device=/dev/video0 ! video/x-raw,width=640,height=480,framerate=30/1 ! videoconvert ! video/x-raw,format=BGR ! queue ! appsink drop=1", cv2.CAP_GSTREAMER)
       if cap.isOpened():
           print('Cam opened')
           break
       print('Waiting for camera')
       time.sleep(1)
    

    writer = cv2.VideoWriter("appsrc ! queue ! multipartmux ! tcpserversink port=8089 ", cv2.CAP_GSTREAMER, 0, 30.0, (640,480)) 
    if not writer.isOpened():
        print('Error: failed to open Writer')
        exit()
    print('Streaming camera')


    while True:
       ret_val, img = cap.read();
       if not ret_val:
          print('Failed to read from camera')
          break

       writer.write(img);
       cv2.waitKey(1)

    writer.release()
    cap.release()

If you don't have opencv build with gstreamer support and don't need to do any processing, you can avoid opencv application and just run from shell:

gst-launch-1.0 v4l2src device=/dev/video0 ! video/x-raw,width=640,height=480,framerate=30/1 ! videoconvert ! video/x-raw,format=BGR ! multipartmux ! tcpserversink port=8089

For B:

#!/usr/bin/env python

import time
import cv2

while True:
    
    while True:
        cap = cv2.VideoCapture("tcpclientsrc host=<IP_of_A> port=8089 ! multipartdemux ! video/x-raw,format=BGR, width=640, height=480, framerate=30/1 ! queue ! appsink drop=1", cv2.CAP_GSTREAMER)
        if cap.isOpened():
            print('Cam stream opened')
            break
        print('Waiting for camera')
        time.sleep(1)
    

    writer = cv2.VideoWriter("appsrc ! video/x-raw,format=BGR,width=640, height=480, framerate=30/1  ! queue ! videoconvert ! video/x-raw,format=I420 ! tee name=t ! queue ! jpegenc ! avimux ! video/x-msvideo ! filesink location=test.avi    t. ! queue ! multipartmux ! tcpserversink port=8192 ", cv2.CAP_GSTREAMER, 0, 30.0, (640,480))
    if not writer.isOpened():
        print('Error: failed to open Writer')
        exit()
    print('writer opened')

    while True:
        ret_val, img = cap.read();
        if not ret_val:
            print('Failed to read from camera')
            break

        #cv2.imshow('Test', img)
        writer.write(img);
        cv2.waitKey(1)

    writer.release()
    cap.release()
    time.sleep(1)

where <IP_of_A> is the IP address of A streaming the camera. Note that the avi file will be overwritten after the source has disconnected/reconnected, you would have to manage file names for that.

For C:

#!/usr/bin/env python

import time
import cv2

while True:
    
    while True:
       cap = cv2.VideoCapture("tcpclientsrc host=<IP_of_B> port=8192 ! multipartdemux ! video/x-raw,format=I420, width=640, height=480, framerate=30/1 ! videoconvert ! video/x-raw,format=BGR ! queue ! appsink drop=1", cv2.CAP_GSTREAMER)
       if cap.isOpened():
           print('Stream opened')
           break
       print('Waiting for server')
       time.sleep(1)


    while True:
       ret_val, img = cap.read();
       if not ret_val:
          print('Failed to read from server')
          break

       cv2.imshow('Received', img)
       cv2.waitKey(1)


    cap.release()
    cv2.destroyAllWindows()
    time.sleep(1)

where <IP_of_B> is the IP address of B.

If you don't have gstreamer support in opencv on C, you can just use :

gst-launch-1.0 tcpclientsrc host=<IP_of_B> port=8192 ! multipartdemux ! video/x-raw,format=I420, width=640, height=480, framerate=30/1 ! videoconvert ! xvimagesink

EDIT: In case only B has static IP, you can have A code to send to tcpclientsink host=<IP_of_B> instead of tcpserversink. So B would use tcpserversrc instead of tcpclientsrc host=<IP_of_A>. The limitation is that cv writer write() function doesn't return any value that could help to detect a failure such as B server being down... The only way I see would be to use a script launching python script and checking its output to detect the warnings issued by gstreamer backend writer code and in such case kill the program and restart a new one.

Upvotes: 3

Related Questions