Fixed picamera mjpeg stream

This commit is contained in:
Fabio Manganiello 2019-12-17 22:44:15 +01:00
parent 634aeec58d
commit 416c9ceb93
4 changed files with 42 additions and 48 deletions

View file

@ -19,21 +19,20 @@ __routes__ = [
def video_feed(): def video_feed():
camera: Optional[CameraPiPlugin] = None camera: Optional[CameraPiPlugin] = None
camera_conf = Config.get('camera.pi') or {}
camera = CameraPiPlugin(**camera_conf)
try: with camera:
camera_conf = Config.get('camera.pi') or {}
camera = CameraPiPlugin(**camera_conf)
while True: while True:
output = camera.get_output_stream() output = camera.get_stream()
with output.ready: with output.ready:
output.ready.wait() output.ready.wait()
frame = output.frame
yield (b'--frame\r\n' if frame and len(frame):
b'Content-Type: image/jpeg\r\n\r\n' + output.frame + b'\r\n') yield (b'--frame\r\n'
finally: b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
if camera:
camera.close_output_stream()
@camera_pi.route('/camera/pi/frame', methods=['GET']) @camera_pi.route('/camera/pi/frame', methods=['GET'])
@ -52,7 +51,6 @@ def get_frame_img():
@authenticate() @authenticate()
def get_stream_feed(): def get_stream_feed():
return Response(video_feed(), return Response(video_feed(),
headers={'Cache-Control': 'no-cache, private', 'Pragma': 'no-cache', 'Age': 0},
mimetype='multipart/x-mixed-replace; boundary=frame') mimetype='multipart/x-mixed-replace; boundary=frame')

View file

@ -1,6 +1,7 @@
{% {%
with pluginIcons = { with pluginIcons = {
'camera': 'fas fa-camera', 'camera': 'fas fa-camera',
'camera.pi': 'fab fa-raspberry-pi',
'camera.ir.mlx90640': 'fas fa-sun', 'camera.ir.mlx90640': 'fas fa-sun',
'execute': 'fas fa-play', 'execute': 'fas fa-play',
'light.hue': 'fa fa-lightbulb', 'light.hue': 'fa fa-lightbulb',

View file

@ -1,4 +1,3 @@
import io
import os import os
import re import re
import shutil import shutil
@ -16,24 +15,6 @@ from platypush.message.event.camera import CameraRecordingStartedEvent, \
from platypush.plugins import Plugin, action from platypush.plugins import Plugin, action
class StreamingOutput:
def __init__(self):
self.frame = None
self.buffer = io.BytesIO()
self.ready = threading.Condition()
def write(self, buf):
if buf.startswith(b'\xff\xd8'):
# New frame, copy the existing buffer's content and notify all clients that it's available
self.buffer.truncate()
with self.ready:
self.frame = self.buffer.getvalue()
self.ready.notify_all()
self.buffer.seek(0)
return self.buffer.write(buf)
class CameraPlugin(Plugin): class CameraPlugin(Plugin):
""" """
Plugin to control generic cameras over OpenCV. Plugin to control generic cameras over OpenCV.

View file

@ -2,6 +2,7 @@
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com> .. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
""" """
import io
import os import os
import socket import socket
import threading import threading
@ -10,7 +11,25 @@ import time
from typing import Optional, Union from typing import Optional, Union
from platypush.plugins import action from platypush.plugins import action
from platypush.plugins.camera import CameraPlugin, StreamingOutput from platypush.plugins.camera import CameraPlugin
class StreamingOutput:
def __init__(self):
self.frame = None
self.buffer = io.BytesIO()
self.ready = threading.Condition()
def write(self, buf):
if buf.startswith(b'\xff\xd8'):
# New frame, copy the existing buffer's content and notify all clients that it's available
self.buffer.truncate()
with self.ready:
self.frame = self.buffer.getvalue()
self.ready.notify_all()
self.buffer.seek(0)
return self.buffer.write(buf)
class CameraPiPlugin(CameraPlugin): class CameraPiPlugin(CameraPlugin):
@ -89,10 +108,12 @@ class CameraPiPlugin(CameraPlugin):
""" """
Close an active connection to the camera. Close an active connection to the camera.
""" """
if not self._camera or self._camera.closed: if self._output and self._camera:
self.logger.info('Camera connection already closed') self._camera.stop_recording()
if self._camera and not self._camera.closed:
self._camera.close()
self._camera.close()
self._camera = None self._camera = None
@action @action
@ -174,23 +195,16 @@ class CameraPiPlugin(CameraPlugin):
if camera and close: if camera and close:
self.close() self.close()
def get_output_stream(self, resize: Union[tuple, list] = None, **opts) -> StreamingOutput: def __enter__(self):
if self._output: camera = self._get_camera()
return self._output
camera = self._get_camera(**opts)
capture_opts = {}
if resize:
capture_opts['resize'] = tuple(resize)
self._output = StreamingOutput() self._output = StreamingOutput()
camera.start_recording(self._output, format='mjpeg', **capture_opts) camera.start_recording(self._output, format='mjpeg')
def get_stream(self):
return self._output return self._output
def close_output_stream(self): def __exit__(self, exc_type, exc_val, exc_tb):
if self._output: self.close()
self._camera.stop_recording()
self._output = None
@action @action
def capture_sequence(self, n_images, directory, name_format='image_%04d.jpg', preview=False, warmup_time=2, def capture_sequence(self, n_images, directory, name_format='image_%04d.jpg', preview=False, warmup_time=2,