base_camera.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import time
  2. import threading
  3. try:
  4. from greenlet import getcurrent as get_ident
  5. except ImportError:
  6. try:
  7. from threading import get_ident
  8. except ImportError:
  9. from _thread import get_ident
  10. class CameraEvent(object):
  11. """An Event-like class that signals all active clients when a new frame is
  12. available.
  13. """
  14. def __init__(self):
  15. self.events = {}
  16. def wait(self):
  17. """Invoked from each client's thread to wait for the next frame."""
  18. ident = get_ident()
  19. if ident not in self.events:
  20. # this is a new client
  21. # add an entry for it in the self.events dict
  22. # each entry has two elements, a threading.Event() and a timestamp
  23. self.events[ident] = [threading.Event(), time.time()]
  24. return self.events[ident][0].wait()
  25. def set(self):
  26. """Invoked by the camera thread when a new frame is available."""
  27. now = time.time()
  28. remove = None
  29. for ident, event in self.events.items():
  30. if not event[0].isSet():
  31. # if this client's event is not set, then set it
  32. # also update the last set timestamp to now
  33. event[0].set()
  34. event[1] = now
  35. else:
  36. # if the client's event is already set, it means the client
  37. # did not process a previous frame
  38. # if the event stays set for more than 5 seconds, then assume
  39. # the client is gone and remove it
  40. if now - event[1] > 5:
  41. remove = ident
  42. if remove:
  43. del self.events[remove]
  44. def clear(self):
  45. """Invoked from each client's thread after a frame was processed."""
  46. self.events[get_ident()][0].clear()
  47. class BaseCamera(object):
  48. thread = None # background thread that reads frames from camera
  49. frame = None # current frame is stored here by background thread
  50. last_access = 0 # time of last client access to the camera
  51. event = CameraEvent()
  52. def __init__(self):
  53. """Start the background camera thread if it isn't running yet."""
  54. if BaseCamera.thread is None:
  55. BaseCamera.last_access = time.time()
  56. # start background frame thread
  57. BaseCamera.thread = threading.Thread(target=self._thread)
  58. BaseCamera.thread.start()
  59. # wait until frames are available
  60. while self.get_frame() is None:
  61. time.sleep(0)
  62. def get_frame(self):
  63. """Return the current camera frame."""
  64. BaseCamera.last_access = time.time()
  65. # wait for a signal from the camera thread
  66. BaseCamera.event.wait()
  67. BaseCamera.event.clear()
  68. return BaseCamera.frame
  69. @staticmethod
  70. def frames():
  71. """"Generator that returns frames from the camera."""
  72. raise RuntimeError('Must be implemented by subclasses.')
  73. @classmethod
  74. def _thread(cls):
  75. """Camera background thread."""
  76. print('Starting camera thread.')
  77. frames_iterator = cls.frames()
  78. for frame in frames_iterator:
  79. BaseCamera.frame = frame
  80. BaseCamera.event.set() # send signal to clients
  81. time.sleep(0)
  82. # if there hasn't been any clients asking for frames in
  83. # the last 10 seconds then stop the thread
  84. if time.time() - BaseCamera.last_access > 10:
  85. frames_iterator.close()
  86. print('Stopping camera thread due to inactivity.')
  87. break
  88. BaseCamera.thread = None