3

I have an AXIS IP camera which I can check it with http and rtsp protocols.

http://ca.me.ra.ip/axis-cgi/mjpg/video.cgi?resolution=1024x768&dummy=1520

rtsp://ca.me.ra.ip:554/axis-media/media.amp?videocodec=h264&resolution=640x480

What I'm trying to do is get the stream, buffer it in my server and then broadcast it, so that with one connection to the camera, I can preview it to multiple users.

I somehow worked it out by this code:

#!/usr/bin/python
'''
    Author: Igor Maculan - n3wtron@gmail.com
    A Simple mjpg stream http server
'''
import cv2
from PIL import Image
import threading
from http.server import BaseHTTPRequestHandler,HTTPServer
from socketserver import ThreadingMixIn
from io import StringIO,BytesIO
import time
capture=None

class CamHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path.endswith('.mjpg'):
            self.send_response(200)
            self.send_header('Content-type','multipart/x-mixed-replace; boundary=--jpgboundary')
            self.end_headers()
            while True:
                try:
                    rc,img = capture.read()
                    if not rc:
                        continue
                    imgRGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
                    jpg = Image.fromarray(imgRGB)
                    tmpFile = BytesIO()
                    jpg.save(tmpFile,'JPEG')
                    self.wfile.write("--jpgboundary".encode())
                    self.send_header('Content-type','image/jpeg')
                    self.send_header('Content-length',str(tmpFile.getbuffer().nbytes))
                    self.end_headers()
                    jpg.save(self.wfile,'JPEG')
                    time.sleep(0.05)
                except KeyboardInterrupt:
                    break
            return
        if self.path.endswith('.html'):
            self.send_response(200)
            self.send_header('Content-type','text/html')
            self.end_headers()
            self.wfile.write('<html><head></head><body>'.encode())
            self.wfile.write('<img src="http://127.0.0.1:8080/cam.mjpg"/>'.encode())
            self.wfile.write('</body></html>'.encode())
            return


class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

def main():
    global capture
    capture = cv2.VideoCapture("rtsp://ca.me.ra.ip:554/axis-media/media.amp?videocodec=h264&resolution=640x480")
    capture.set(cv2.CAP_PROP_FRAME_WIDTH, 320); 
    capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 240);
    capture.set(cv2.CAP_PROP_SATURATION,0.2);
    global img
    try:
        server = ThreadedHTTPServer(('0.0.0.0', 8080), CamHandler)
        print( "server started")
        server.serve_forever()
    except KeyboardInterrupt:
        capture.release()
        server.socket.close()

if __name__ == '__main__':
    main()

Then I can check the camera in local machine using this address in my browser:

http://ca.me.ra.ip:8080/cam.mjpg

The problem is if I open it in two browsers the servers crashes with this error:

Assertion fctx->async_lock failed at libavcodec/pthread_frame.c:155

Link to pthread_frame.c

  152 static void async_unlock(FrameThreadContext *fctx)
  153 {
  154     pthread_mutex_lock(&fctx->async_mutex);
  155     av_assert0(fctx->async_lock);   <-- This is the line which throws error
  156     fctx->async_lock = 0;
  157     pthread_cond_broadcast(&fctx->async_cond);
  158     pthread_mutex_unlock(&fctx->async_mutex);
  159 }

If I change camera address to capture = cv2.VideoCapture(0) in local machine to broadcast my laptop webcam, everything works fine no matter how many browsers I run.

Is there a way I can fix this issue without changing the camera address as shown above?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Ghasem
  • 14,455
  • 21
  • 138
  • 171

1 Answers1

0

I think this problem stems from two different 'do_GET()'s competing for the camera's frame data.

A solution would be to move your camera capture code out of your CamHandler back into main, or its own thread, while also moving your server code into its own thread.

Feed the camera frames into a collections.deque which you can pass a link to your HTTPRequestHandler using partial and allow any connected devices to use the first element of the deque.

This was put together from information here: https://stackoverflow.com/a/46224191 and https://stackoverflow.com/a/52046062

Here is loose code of what I've done for this.

import collections
import cv2
from http.server import BaseHTTPRequestHandler, HTTPServer
import socket
import threading

camtoview = collections.deque(maxlen=5)

class CamHandler(BaseHTTPRequestHandler):
  def __init__(self,camtoview, *args, **kwargs):
    self.camtoview = camtoview
    super().__init__(*args,**kwargs)
  def do_GET(self):
    if self.path.endswith('.mjpg'):
      self.send_response(200)
      self.send_header('Content-type','multipart/x-mixed-replace; boundary=--jpgboundary')
      self.end_headers()
      try:
        while True:
          if len(self.camtoview):
            curfram = self.camtoview[0]
            flag, curfram = cv2.imencode(".jpg",curfram)
            self.wfile.write(b'--FRAME\r\n')
            self.send_header('Content-Type','image/jpeg')
            self.send_header('Content-Length', len(curfram))
            send.end_headers()
            self.wfile.write(curfram)
      except Exception as exception:
        pass

class webThread(threading.Thread):
  def __init__(self,sock,addr,cam):
    threading.Thread.__init__(self)
    self.camtoview = cam
    self.sock = sock
    self.addr = addr
    self.daemon = True
    self.start()
  def run(self):
    camhanded = partial(CamHandler, self.camtoview)
    httpd = HTTPServer((self.addr[0],self.addr[1]),camhanded,False)
    httpd.socket = self.sock
    httpd.server_bind = self.server_close = lambda self: None
    httpd.serve_forever()

if __name__ == "__main__":
  addr = ("localhost",8080)
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
  sock.bind(addr)
  sock.listen(5)
  [webThread(sock,addr,camtoview) for i in range(50)]
  ###
  #infinite camera loop down here#
  while True:
    #camera code
    camtoview.append(newcameraframe)