Skip to content

Commit 96a8c34

Browse files
committed
Re-work preservation of timestamps
We don't need all those extra encoder fields, and it'd be nice if the split_recording method could return the entire frame meta-data rather than just the timestamp. This also re-works "None" timestamps in general. We can reasonably assume the camera's clock never goes backward during a recording so in the event of a "None" timestamp we should *always* repeat the last timestamp.
1 parent 4f84e2e commit 96a8c34

File tree

3 files changed

+37
-52
lines changed

3 files changed

+37
-52
lines changed

picamera/camera.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,9 @@ def split_recording(self, output, splitter_port=1, **options):
10861086
and *inline_headers* must be ``True`` when :meth:`start_recording` is
10871087
called (this is the default).
10881088
1089+
The method returns the meta-data of the first :class:`PiVideoFrame`
1090+
that is written to the new output.
1091+
10891092
.. versionchanged:: 1.3
10901093
The *splitter_port* parameter was added
10911094

picamera/encoders.py

Lines changed: 31 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -600,11 +600,8 @@ def __init__(
600600
super(PiVideoEncoder, self).__init__(
601601
parent, camera_port, input_port, format, resize, **options)
602602
self._next_output = []
603+
self._split_frame = None
603604
self.frame = None
604-
self.chunk_timestamp = None
605-
self.next_pts = 0
606-
self.last_pts = 0
607-
self.split_pts = 0
608605

609606
def _create_encoder(
610607
self, format, bitrate=17000000, intra_period=None, profile='high',
@@ -811,7 +808,6 @@ def start(self, output, motion_output=None):
811808
timestamp=0,
812809
complete=False,
813810
)
814-
self.chunk_timestamp = None
815811
if motion_output is not None:
816812
self._open_output(motion_output, PiVideoFrameType.motion_data)
817813
super(PiVideoEncoder, self).start(output)
@@ -849,7 +845,7 @@ def split(self, output, motion_output=None):
849845
# intra_period / framerate gives the time between I-frames (which
850846
# should also coincide with SPS headers). We multiply by three to
851847
# ensure the timeout is deliberately excessive, and clamp the minimum
852-
# timeout to 10 seconds (otherwise unencoded formats tend to fail
848+
# timeout to 15 seconds (otherwise unencoded formats tend to fail
853849
# presumably due to I/O capacity)
854850
if self.parent:
855851
framerate = self.parent.framerate + self.parent.framerate_delta
@@ -861,37 +857,20 @@ def split(self, output, motion_output=None):
861857
if not self.event.wait(timeout):
862858
raise PiCameraRuntimeError('Timed out waiting for a split point')
863859
self.event.clear()
864-
return self.split_pts
860+
return self._split_frame
865861

866862
def _callback_write(self, buf, key=PiVideoFrameType.frame):
867863
"""
868864
Extended to implement video frame meta-data tracking, and to handle
869865
splitting video recording to the next output when :meth:`split` is
870866
called.
871867
"""
872-
timestamp = (
873-
None
874-
if buf.pts in (0, mmal.MMAL_TIME_UNKNOWN) else
875-
buf.pts
876-
)
877-
complete = bool(buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_FRAME_END)
878-
# Uncomment to observe skipped frames:
879-
# if timestamp:
880-
# while self.next_pts and timestamp > self.next_pts:
881-
# print("<SKIP!>")
882-
# self.next_pts += 1000000 / self.parent.framerate
883-
# self.next_pts = timestamp + 1000000 / self.parent.framerate
884-
if timestamp is None:
885-
timestamp = self.chunk_timestamp
886-
if complete:
887-
self.chunk_timestamp = None
888-
else:
889-
self.chunk_timestamp = timestamp
890-
self.frame = PiVideoFrame(
868+
last_frame = self.frame
869+
this_frame = PiVideoFrame(
891870
index=
892-
self.frame.index + 1
893-
if self.frame.complete else
894-
self.frame.index,
871+
last_frame.index + 1
872+
if last_frame.complete else
873+
last_frame.index,
895874
frame_type=
896875
PiVideoFrameType.key_frame
897876
if buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_KEYFRAME else
@@ -902,18 +881,24 @@ def _callback_write(self, buf, key=PiVideoFrameType.frame):
902881
PiVideoFrameType.frame,
903882
frame_size=
904883
buf.length
905-
if self.frame.complete else
906-
self.frame.frame_size + buf.length,
884+
if last_frame.complete else
885+
last_frame.frame_size + buf.length,
907886
video_size=
908-
self.frame.video_size
887+
last_frame.video_size
909888
if buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_CODECSIDEINFO else
910-
self.frame.video_size + buf.length,
889+
last_frame.video_size + buf.length,
911890
split_size=
912-
self.frame.split_size
891+
last_frame.split_size
913892
if buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_CODECSIDEINFO else
914-
self.frame.split_size + buf.length,
915-
timestamp=timestamp,
916-
complete=complete,
893+
last_frame.split_size + buf.length,
894+
timestamp=
895+
# Time cannot go backwards, so if we've got an unknown pts
896+
# simply repeat the last one
897+
last_frame.timestamp
898+
if buf.pts in (0, mmal.MMAL_TIME_UNKNOWN) else
899+
buf.pts,
900+
complete=
901+
bool(buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_FRAME_END)
917902
)
918903
if self._intra_period == 1 or (buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_CONFIG):
919904
with self.outputs_lock:
@@ -926,21 +911,20 @@ def _callback_write(self, buf, key=PiVideoFrameType.frame):
926911
self._close_output(new_key)
927912
self._open_output(new_output, new_key)
928913
if new_key == PiVideoFrameType.frame:
929-
self.frame = PiVideoFrame(
930-
index=self.frame.index,
931-
frame_type=self.frame.frame_type,
932-
frame_size=self.frame.frame_size,
933-
video_size=self.frame.video_size,
914+
this_frame = PiVideoFrame(
915+
index=this_frame.index,
916+
frame_type=this_frame.frame_type,
917+
frame_size=this_frame.frame_size,
918+
video_size=this_frame.video_size,
934919
split_size=0,
935-
timestamp=self.frame.timestamp,
936-
complete=self.frame.complete,
920+
timestamp=this_frame.timestamp,
921+
complete=this_frame.complete,
937922
)
938-
self.split_pts = self.last_pts
923+
self._split_frame = this_frame
939924
self.event.set()
940925
if buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_CODECSIDEINFO:
941926
key = PiVideoFrameType.motion_data
942-
if self.frame.timestamp:
943-
self.last_pts = self.frame.timestamp
927+
self.frame = this_frame
944928
return super(PiVideoEncoder, self)._callback_write(buf, key)
945929

946930

picamera/frames.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,9 @@ class PiVideoFrame(namedtuple('PiVideoFrame', (
150150
.. warning::
151151
152152
Currently, the camera occasionally returns "time unknown" values in
153-
this field which picamera represents as ``None``. If you are
154-
querying this property you will need to check the value is not
155-
``None`` before using it. This happens for SPS header "frames",
156-
for example.
153+
this field. In this case, picamera will simply re-use the timestamp
154+
of the previous frame (under the assumption that time never goes
155+
backwards). This happens for SPS header "frames", for example.
157156
158157
.. attribute:: complete
159158
@@ -213,4 +212,3 @@ def header(self):
213212
'PiVideoFrame.frame_type for equality with '
214213
'PiVideoFrameType.sps_header instead'))
215214
return self.frame_type == PiVideoFrameType.sps_header
216-

0 commit comments

Comments
 (0)