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