3

so I am trying to make a streamer that streams video from one computer to another (or the same one, for now) on my LAN. I need it to use as little bandwidth as possible so I am trying to encode in h264. I'm having trouble doing this, and I don't really know where to start. Right now it is encoded in jpg, and it is sending frame by frame. I am aware, however, that this is very inefficient and it consumes a lot of bandwidth. This is my current receiver code.

import cv2
import socket
import _pickle
import time

host = "192.168.1.196"
port = 25565
boo = True

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # declares s object with two parameters
s.bind((host, port)) # tells my socket object to connect to this host & port "binds it to it"
s.listen(10) # tells the socket how much data it will be receiving.

conn, addr = s.accept()
buf = ''
while boo:
        pictures = conn.recv(128000) # creates a pictures variable that receives the pictures with a max amount of 128000 data it can receive
        decoded = _pickle.loads(pictures) # decodes the pictures
        frame = cv2.imdecode(decoded, cv2.IMREAD_COLOR) # translates decoded into frames that we can see!
        cv2.imshow("recv", frame)
        if cv2.waitKey(1) & 0xFF == ord("q"):  # wait until q key was pressed once and
            break

And here is my current client code (sender):

import cv2
import numpy as np
import socket
import _pickle
from cv2 import *

host = "192.168.1.196"
port = 25565

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # declares s object with two parameters
s.connect((host, port))  # connects to the host & port
cap = cv2.VideoCapture(1)
cv2.cv.CV_FOURCC('H','2','6','4')
while cap.isOpened(): # while camera is being used
    ret, frame = cap.read()  # reads each frame from webcam
    cv2.imshow("client", frame)
    if ret:
        encoded = _pickle.dumps(cv2.imencode("fourcc", frame)[1]) # encoding each frame, instead of sending live video it is sending pictures one by one
        s.sendall(encoded)
    if cv2.waitKey(1) & 0xFF == ord("q"): # wait until key was pressed once and
        break
cap.release()
cv2.destroyAllWindows()

I just need some help on how to encode the video and decode it in h264.

Coke
  • 51
  • 1
  • 5

1 Answers1

3

You can do this using pyzmq and the the publish/subscribe pattern with base64 string encoding/decoding. On the server side, the idea is:

  • Get frame from camera stream
  • Read image from memory buffer with cv2.imencode
  • Convert ndarray into str with base64 and send over the socket

On the client side we simply reverse the process:

  • Read image string from socket
  • Convert str into bytes with base64
  • Convert bytes into ndarray with np.frombuffer + cv2.imdecode

This method shouldn't use much bandwidth since its only sending strings across the socket.


Server

import base64
import cv2
import zmq

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.connect('tcp://localhost:7777')

camera = cv2.VideoCapture(0)

while True:
    try:
        ret, frame = camera.read()
        frame = cv2.resize(frame, (640, 480))
        encoded, buf = cv2.imencode('.jpg', frame)
        image = base64.b64encode(buf)
        socket.send(image)
    except KeyboardInterrupt:
        camera.release()
        cv2.destroyAllWindows()
        break

Client

import cv2
import zmq
import base64
import numpy as np

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.bind('tcp://*:7777')
socket.setsockopt_string(zmq.SUBSCRIBE, np.unicode(''))

while True:
    try:
        image_string = socket.recv_string()
        raw_image = base64.b64decode(image_string)
        image = np.frombuffer(raw_image, dtype=np.uint8)
        frame = cv2.imdecode(image, 1)
        cv2.imshow("frame", frame)
        cv2.waitKey(1)
    except KeyboardInterrupt:
        cv2.destroyAllWindows()
        break
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • Thanks. I'll try this out tomorrow, and I'll let you know how it goes. I have a max cap of 4 mbit/s for this streaming encoder (for some reason... rules...) and hopefully it works out well. – Coke Dec 04 '19 at 02:52
  • 1
    Thanks, I continued to use the socket module rather than ZeroMQ, and encoded in base64 but I'm still doing 9mbps. I need it to be less than 4 mbps. Any idea on how to encode the video in h.264? – Coke Dec 04 '19 at 23:40
  • Does resizing the frame smaller before sending it to reduce bandwidth work? I'm not sure how to encode the video in h.264 – nathancy Dec 04 '19 at 23:59
  • Pretty coincidental, I did just that, and grayscaled it, and now I'm getting <4 mbps. – Coke Dec 05 '19 at 12:38
  • Thanks a ton for your help dude. – Coke Dec 05 '19 at 12:38
  • @Coke one thing to note is that the lower you reduce the frame before sending it, the more detail you will lose when you enlarge it on the other side. I recommend finding a balance to only reduce it as much as required. Glad its working for you! – nathancy Dec 05 '19 at 20:22
  • that's fine, i'm getting 500 kbps now and thats amazing for my needs. – Coke Dec 17 '19 at 03:58
  • Yeah for sure. Now I've just gotta get opencv working on my pi4, that's fun... – Coke Dec 20 '19 at 18:37
  • The question mentions efficiency, and this starts by making it a lot worse. Base64 turns 3 bytes into 4, adding an extra 33%. And why? ZeroMQ messages are binary. – MSalters Jul 07 '21 at 14:01
  • Thank you very much. But I read elsewhere that PyZMQ has security issues with encoding and decoding and it's recommend to use a 3rd party library. What do you think please? – Avv Nov 02 '22 at 19:42