miguelgrinberg / flask-video-streaming

Supporting code for my article on video streaming with Flask.
http://blog.miguelgrinberg.com/post/video-streaming-with-flask
MIT License
1.41k stars 524 forks source link

Multiple camera streams at the same time #11

Closed kizniche closed 5 years ago

kizniche commented 7 years ago

Thanks for publishing this code. It works great and I'm able to stream both a Pi camera or a USB camera. However, I'm having difficulty getting more than one to stream at the same time. I'm running the Flask app in apache2 using the WSGI module.

I've tried calling the same route with different parameters to access the different devices, and this works, but not both at the same time. The first call starts a stream, then the next only displays the first cam's images, so I have two of the same stream on the same page.

@blueprint.route('/video_feed/<camera_type>/<device>')
def video_feed(camera_type, device):
    """Video streaming route. Put this in the src attribute of an img tag."""
    camera_stream = import_module('mycodo.mycodo_flask.camera.camera_' + camera_type).Camera
    return Response(gen(camera_stream(opencv_device=int(device))),
                    mimetype='multipart/x-mixed-replace; boundary=frame')
<img src="/video_feed/opencv/0">
<img src="/video_feed/picamera/0">

I then tried making two different routes, but that produced the same result as the first method.

@blueprint.route('/video_opencv/<device>')
def video_opencv(device):
    """Video streaming route. Put this in the src attribute of an img tag."""
    from mycodo.mycodo_flask.camera.camera_opencv import Camera
    Camera.set_video_source(int(device))
    return Response(gen(Camera()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@blueprint.route('/video_picamera')
def video_picamera():
    """Video streaming route. Put this in the src attribute of an img tag."""
    from mycodo.mycodo_flask.camera.camera_picamera import Camera
    return Response(gen(Camera()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')
<img src="/video_opencv/0">
<img src="/video_picamera">

Could you help me understand what I'm missing in trying to get this to work? Thanks.

miguelgrinberg commented 7 years ago

The BaseCamera has a bunch of class variables (here). These are going to be shared across all subclasses. This prevents having more than one active camera at a time. To support multiple cameras, you should refactor that so that these variable which keep the state of the background camera task are separate for each implementation.

kizniche commented 7 years ago

Thank you for the quick response. That makes sense and is what I had a hunch about but didn't get a chance to test. I'll work on it this weekend and report back with what I come up with to help anyone else who may come across this issue. Thanks, again.

kizniche commented 7 years ago

I got this working, thanks to your tip. Here is what I changed in order to separate the camera instances. This may or may not be best practice, but it was the first thing that came to mind. The following is what I changed:

HTML:

{% for each_camera in cameras %}
<img src="/video_feed/opencv/{{each_camera.opencv_device}}">
{% endif %}
<img src="/video_feed/picamera/0">

Flask route:

@app.route('/video_feed/<camera_type>/<device>')
def video_feed(camera_type, device):
    """Video streaming route. Put this in the src attribute of an img tag."""
    camera_stream = import_module('camera_' + camera_type).Camera
    if camera_type == 'opencv':
        camera_stream.set_video_source(int(device))
    return Response(gen(camera_stream(camera_type=camera_type, device=int(device))),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

base_camera.py:

class BaseCamera(object):
    thread = {}  # background thread that reads frames from camera
    frame = {}  # current frame is stored here by background thread
    last_access = {}  # time of last client access to the camera
    event = {}

    def __init__(self, camera_type=None, device=None):
        """Start the background camera thread if it isn't running yet."""
        self.unique_name = "{cam}_{dev}".format(cam=camera_type, dev=device)
        BaseCamera.event[self.unique_name] = CameraEvent()
        if self.unique_name not in BaseCamera.thread:
            BaseCamera.thread[self.unique_name] = None
        if BaseCamera.thread[self.unique_name] is None:
            BaseCamera.last_access[self.unique_name] = time.time()

            # start background frame thread
            BaseCamera.thread[self.unique_name] = threading.Thread(target=self._thread,
                                                                   args=(self.unique_name,))
            BaseCamera.thread[self.unique_name].start()

            # wait until frames are available
            while self.get_frame() is None:
                time.sleep(0)

    def get_frame(self):
        """Return the current camera frame."""
        BaseCamera.last_access[self.unique_name] = time.time()

        # wait for a signal from the camera thread
        BaseCamera.event[self.unique_name].wait()
        BaseCamera.event[self.unique_name].clear()

        return BaseCamera.frame[self.unique_name]

    @staticmethod
    def frames():
        """"Generator that returns frames from the camera."""
        raise RuntimeError('Must be implemented by subclasses')

    @classmethod
    def _thread(cls, unique_name):
        """Camera background thread."""
        print('Starting camera thread')
        frames_iterator = cls.frames()
        for frame in frames_iterator:
            BaseCamera.frame[unique_name] = frame
            BaseCamera.event[unique_name].set()  # send signal to clients
            time.sleep(0)

            # if there hasn't been any clients asking for frames in
            # the last 5 seconds then stop the thread
            if time.time() - BaseCamera.last_access[unique_name] > 5:
                frames_iterator.close()
                print('Stopping camera thread due to inactivity')
                break
        BaseCamera.thread[unique_name] = None

Many thanks, again, for the work you put into this code. I've integrated it into my project, Mycodo. I'll put a proper attribution to you and your repo before I make the next release.

Cheers!

miguelgrinberg commented 7 years ago

Oh, that's a clever solution, great!

miguelgrinberg commented 5 years ago

This issue will be automatically closed due to being inactive for more than six months. Seeing that I haven't responded to your last comment, it is quite possible that I have dropped the ball on this issue and I apologize about that. If that is the case, do not take the closing of the issue personally as it is an automated process doing it, just reopen it and I'll get back to you.

rajeshcis commented 4 years ago

Hello @kizniche, Thank you for the above code, I am using the same concept to stream the live feeds from cameras into the flask application. I am stuck on stoping the thread which is already started, I have added multiple video streams dynamically but I want to stop this thread using a different controller.

Please can you help me out with this?

Thanks in advance.

kizniche commented 4 years ago

@rajeshcis The following is how the camera threads are stopped in Mycodo, either purposefully or from inactivity: https://github.com/kizniche/Mycodo/blob/49a7fcd9f04aba4f09b5e792c9069a4e528b7690/mycodo/mycodo_flask/camera/base_camera.py#L113-L124

ODN81HC commented 4 years ago

Hello @kizniche, As I am a newbie in Flask, I didn't quite get the part in the HTML where you put the each_camera.opencv_device to get multiple openCV cameras into one video stream page. Where would I include a list of openCV in so that the HTML knows the endpoints route of my openCV cameras? Thank you.

biggidvs commented 4 years ago

Hey @kizniche ,

I saw your post above regarding what you changed to allow 2 cameras to stream at once. Is what you posted the only changes you made? I'm looking to stream 2 USB cameras with OpenCV at the same time. Wasn't sure if those were the only changes needed to get it up and running.

joekrom commented 4 years ago

hi i am new here , i want to integrate video streaming in my mircoservices architecture using docker. i am able to stream video on one page, but when i change the page and come back i got errors , there is no image no more whilst the camera is still activated . i would like to know how to handle this issue.