Skip to content

Commit

Permalink
Merge branch 'av-sync'
Browse files Browse the repository at this point in the history
  • Loading branch information
waveform80 committed Nov 2, 2018
2 parents 2dbe427 + ff9b17a commit 95fb639
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 165 deletions.
5 changes: 4 additions & 1 deletion picamera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,9 @@ def split_recording(self, output, splitter_port=1, **options):
and *inline_headers* must be ``True`` when :meth:`start_recording` is
called (this is the default).
The method returns the meta-data of the first :class:`PiVideoFrame`
that is written to the new output.
.. versionchanged:: 1.3
The *splitter_port* parameter was added
Expand All @@ -1103,7 +1106,7 @@ def split_recording(self, output, splitter_port=1, **options):
'There is no recording in progress on '
'port %d' % splitter_port)
else:
encoder.split(output, options.get('motion_output'))
return encoder.split(output, options.get('motion_output'))

def request_key_frame(self, splitter_port=1):
"""
Expand Down
47 changes: 27 additions & 20 deletions picamera/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ def __init__(
super(PiVideoEncoder, self).__init__(
parent, camera_port, input_port, format, resize, **options)
self._next_output = []
self._split_frame = None
self.frame = None

def _create_encoder(
Expand Down Expand Up @@ -844,7 +845,7 @@ def split(self, output, motion_output=None):
# intra_period / framerate gives the time between I-frames (which
# should also coincide with SPS headers). We multiply by three to
# ensure the timeout is deliberately excessive, and clamp the minimum
# timeout to 10 seconds (otherwise unencoded formats tend to fail
# timeout to 15 seconds (otherwise unencoded formats tend to fail
# presumably due to I/O capacity)
if self.parent:
framerate = self.parent.framerate + self.parent.framerate_delta
Expand All @@ -856,18 +857,20 @@ def split(self, output, motion_output=None):
if not self.event.wait(timeout):
raise PiCameraRuntimeError('Timed out waiting for a split point')
self.event.clear()
return self._split_frame

def _callback_write(self, buf, key=PiVideoFrameType.frame):
"""
Extended to implement video frame meta-data tracking, and to handle
splitting video recording to the next output when :meth:`split` is
called.
"""
self.frame = PiVideoFrame(
last_frame = self.frame
this_frame = PiVideoFrame(
index=
self.frame.index + 1
if self.frame.complete else
self.frame.index,
last_frame.index + 1
if last_frame.complete else
last_frame.index,
frame_type=
PiVideoFrameType.key_frame
if buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_KEYFRAME else
Expand All @@ -878,22 +881,24 @@ def _callback_write(self, buf, key=PiVideoFrameType.frame):
PiVideoFrameType.frame,
frame_size=
buf.length
if self.frame.complete else
self.frame.frame_size + buf.length,
if last_frame.complete else
last_frame.frame_size + buf.length,
video_size=
self.frame.video_size
last_frame.video_size
if buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_CODECSIDEINFO else
self.frame.video_size + buf.length,
last_frame.video_size + buf.length,
split_size=
self.frame.split_size
last_frame.split_size
if buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_CODECSIDEINFO else
self.frame.split_size + buf.length,
last_frame.split_size + buf.length,
timestamp=
None
# Time cannot go backwards, so if we've got an unknown pts
# simply repeat the last one
last_frame.timestamp
if buf.pts in (0, mmal.MMAL_TIME_UNKNOWN) else
buf.pts,
complete=
bool(buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_FRAME_END),
bool(buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_FRAME_END)
)
if self._intra_period == 1 or (buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_CONFIG):
with self.outputs_lock:
Expand All @@ -906,18 +911,20 @@ def _callback_write(self, buf, key=PiVideoFrameType.frame):
self._close_output(new_key)
self._open_output(new_output, new_key)
if new_key == PiVideoFrameType.frame:
self.frame = PiVideoFrame(
index=self.frame.index,
frame_type=self.frame.frame_type,
frame_size=self.frame.frame_size,
video_size=self.frame.video_size,
this_frame = PiVideoFrame(
index=this_frame.index,
frame_type=this_frame.frame_type,
frame_size=this_frame.frame_size,
video_size=this_frame.video_size,
split_size=0,
timestamp=self.frame.timestamp,
complete=self.frame.complete,
timestamp=this_frame.timestamp,
complete=this_frame.complete,
)
self._split_frame = this_frame
self.event.set()
if buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_CODECSIDEINFO:
key = PiVideoFrameType.motion_data
self.frame = this_frame
return super(PiVideoEncoder, self)._callback_write(buf, key)


Expand Down
8 changes: 3 additions & 5 deletions picamera/frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,9 @@ class PiVideoFrame(namedtuple('PiVideoFrame', (
.. warning::
Currently, the camera occasionally returns "time unknown" values in
this field which picamera represents as ``None``. If you are
querying this property you will need to check the value is not
``None`` before using it. This happens for SPS header "frames",
for example.
this field. In this case, picamera will simply re-use the timestamp
of the previous frame (under the assumption that time never goes
backwards). This happens for SPS header "frames", for example.
.. attribute:: complete
Expand Down Expand Up @@ -213,4 +212,3 @@ def header(self):
'PiVideoFrame.frame_type for equality with '
'PiVideoFrameType.sps_header instead'))
return self.frame_type == PiVideoFrameType.sps_header

0 comments on commit 95fb639

Please sign in to comment.