-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCameraManager.py
206 lines (149 loc) · 7.66 KB
/
CameraManager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import io
import os
import subprocess
from picamera2 import Picamera2
from picamera2.encoders import Encoder
from picamera2.outputs import FileOutput
from datetime import datetime
class Camera:
"""
A wrapper to interface with the Pi camera with implementations to capture a frame for preview, start and stop a recording.
This interface will record and capture the 'main' stream which is set up as the processed 'raw' feed.
"""
def __init__(self, width, height, rate, crop_w, crop_h):
# Initialise Pi camera instance
self.picam2 = Picamera2()
# Pi camera configuration:
self.picam2.video_configuration.size = (width, height) # Capture video at defined resolution/size
self.picam2.video_configuration.controls.FrameRate = rate # Capture video at defined framerate
self.picam2.video_configuration.encode = 'main' # Use 'main' stream for encoder
self.picam2.video_configuration.format = 'BGR888' # Use this format for OpenCV compatibility
self.picam2.video_configuration.align() # Align stream size (THIS CHANGES THE RES SLIGHTLY!)
# Apply the configuration to the Pi camera
self.picam2.configure("video")
# Print camera configurations to confirm correct set up
print(self.picam2.camera_configuration()['sensor'])
print(self.picam2.camera_configuration()['main'])
print(self.picam2.camera_configuration()['controls'])
# Start the Pi camera
self.picam2.start()
# Variable to store whether recording is in progress
self.recording = False
# Initialise variable to store recording filename and timestamps
self.recording_filename = ""
self.recording_start_ts = None
self.recording_stop_ts = None
# Initialise cropping parameters
self.crop_w = crop_w
self.crop_h = crop_h
def getStatus(self):
""" Return whether or not recording is in progress. """
return self.recording
def captureFrame(self):
""" Capture a frame from the camera for constructing a frame-by-frame preview feed. """
frame = self.picam2.capture_array("main")
if (self.crop_w is not None) and (self.crop_h is not None):
frame = frame[self.crop_h[0]:self.crop_h[1], self.crop_w[0]:self.crop_w[1]]
return frame
def startRecording(self):
""" Start recording from the Pi camera to the memory buffer. """
# Only start is not already recording
if not self.recording:
# Set recording status
self.recording = True
# Initialise a memory buffer to store video recording
self.recording_membuff = io.BytesIO()
self.recording_output = FileOutput(self.recording_membuff)
# Generate starting timestamp
self.recording_start_ts = datetime.now()
self.recording_start_ts_str = self.recording_start_ts.strftime("%m-%d-%Y-%H-%M-%S")
# Generate filename
self.recording_filename = "recording_" + self.recording_start_ts_str
# Reset recording end timestamp
self.recording_stop_ts = None
# Initialise a 'null' encoder to record without any encoding
self.recording_encoder = Encoder()
# Start the recording
self.picam2.start_recording(
encoder=self.recording_encoder, # Null encoder for 'raw' footage
output=self.recording_output # Record directly to memory
)
# Log to console
print("Started recording:", self.recording_filename)
else:
# Log to console if user requests recording start when already in progress
print("Cannot start recording, existing recording is in progress!")
def stopRecording(self):
""" Stop recording, unload from mamory buffer to disk and convert to MKV with the lossless FFV1 codec. """
# Only stop recording if already in progress
if self.recording:
# Stop the recording
self.picam2.stop_recording()
# stop_recording() entirely stops the Pi camera with stop()
self.picam2.start()
# Generate stopped timestamp
self.recording_stop_ts = datetime.now()
self.recording_stop_ts_str = self.recording_stop_ts.strftime("%m-%d-%Y-%H-%M-%S")
# Log to console
print("Stopped recording:"
+ self.recording_filename
+ " (Stopped at: "
+ self.recording_stop_ts_str
+ ")"
)
print("Please wait, unloading recording from memory buffer...")
# Save to file
with open(self.recording_filename, "xb") as file:
file.write(self.recording_membuff.getbuffer())
# Reset recording start timestamp
self.recording_start_ts = None
# Close recording memory buffer to discard data and clear RAM space
self.recording_membuff.close()
# Generate encoded recording filename
self.recording_filename_mkv = self.recording_filename + ".mkv"
# Log to console
print("Please wait, encoding raw recording to lossless mkv...: " + self.recording_filename_mkv)
# Get the aligned resolution of the recording
(aligned_width, aligned_height) = self.picam2.camera_configuration()['main']['size']
# FFmpeg command to convert video from BGR888 to MKV (lossless FFV1 codec)
ffmpeg_command = [
'ffmpeg', # call FFmpeg
'-f', 'rawvideo', # force raw data type
'-s', f'{aligned_width}x{aligned_height}', # width and height
'-pix_fmt', 'bgr24', # set BGR888 format
'-i', self.recording_filename, # filename of raw file
'-c:v', 'ffv1', # lossless FFV1 codec
'-y', # overwrite output file if exists
'-loglevel', 'error', # only show errors in console
'-stats', # show progress stats
self.recording_filename_mkv # output filename
]
# Run FFmpeg command as a subprocess
subprocess.run(ffmpeg_command)
# Delete raw file
os.remove(self.recording_filename)
# Log to console
print("Write complete, it is safe to exit or record again: " + self.recording_filename_mkv)
# Set the recording status to false to allow program exiting
self.recording = False
else:
# If not already recording, log error to console
print("Cannot stop recording, program is currently not recording anything!")
def toggleRecording(self):
""" Toggle the Pi camera recording. """
# Turn off if already on
if self.recording:
self.stopRecording()
# Turn on light if already off
else:
self.startRecording()
def shutdown(self):
""" Gracefully stop the Pi camera instance. """
# If recording in progress, stop it
if self.recording:
print("Exit request received while recording - recording will be stopped.")
self.stopRecording()
# Stop the Pi camera instance.
self.picam2.stop()
# Log to console
print("Camera instance has been gracefully stopped.")