Skip to content

Commit 0c59977

Browse files
committed
Fix V4L2 encoder when no encoded packets are produced for a frame
Also watch out for cases where we get an SPS, a PPS but then no frame (this is the I-frame equivalent of the nothing-at-all case which is what happens for P-frames). These should not be output either. Signed-off-by: David Plowman <[email protected]>
1 parent ca5c06f commit 0c59977

File tree

1 file changed

+35
-9
lines changed

1 file changed

+35
-9
lines changed

picamera2/encoders/v4l2_encoder.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,30 @@ def _stop(self):
174174
fcntl.ioctl(self.vd, VIDIOC_REQBUFS, reqbufs)
175175
self.vd.close()
176176

177+
def _check_for_picture(self, buf):
178+
"""Check whether the encoded buffer actually contains a picture."""
179+
# At low bitrates, we can get a buffer with an SPS and a PPS but no picture.
180+
# We must avoid writing these to the output. However, an SPS and PPS together
181+
# can't be very large, so assume anything larger than this must be OK:
182+
if len(buf) > 64:
183+
return True
184+
185+
# Search the start codes to see if there's a picture here. Start codes are
186+
# always the four bytes 0 0 0 1, and the low nibble of the subsequent byte tells
187+
# us what's we've got.
188+
start_code = b'\x00\x00\x00\x01' # H.264 start code sequence
189+
picture_codes = {5, 1} # IDR and non-IDR pictures
190+
pos = 0
191+
while (pos := buf.find(start_code, pos)) != -1:
192+
pos += 4
193+
if pos < len(buf) and buf[pos] & 15 in picture_codes:
194+
return True
195+
return False
196+
177197
def thread_poll(self, buf_available):
178198
"""Outputs encoded frames"""
179199
pollit = select.poll()
180-
pollit.register(self.vd, select.POLLIN)
200+
pollit.register(self.vd, select.POLLIN | select.POLLOUT)
181201

182202
while self._running or self.buf_frame.qsize() > 0:
183203
events = pollit.poll(400)
@@ -194,8 +214,11 @@ def thread_poll(self, buf_available):
194214
queue_item.release()
195215
break
196216

197-
for _, event in events:
198-
if event & select.POLLIN:
217+
for fd_event, event in events:
218+
if fd_event != self.vd.fileno():
219+
continue
220+
221+
if event & select.POLLOUT:
199222
buf = v4l2_buffer()
200223
planes = v4l2_plane * VIDEO_MAX_PLANES
201224
planes = planes()
@@ -208,11 +231,17 @@ def thread_poll(self, buf_available):
208231
if ret == 0:
209232
buf_available.put(buf.index)
210233

234+
# Release frame from camera
235+
queue_item = self.buf_frame.get()
236+
queue_item.release()
237+
238+
if event & select.POLLIN:
211239
buf = v4l2_buffer()
212240
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
213241
buf.memory = V4L2_MEMORY_MMAP
214242
buf.length = 1
215-
ctypes.memset(planes, 0, ctypes.sizeof(v4l2_plane) * VIDEO_MAX_PLANES)
243+
planes = v4l2_plane * VIDEO_MAX_PLANES
244+
planes = planes()
216245
buf.m.planes = planes
217246
ret = fcntl.ioctl(self.vd, VIDIOC_DQBUF, buf)
218247
keyframe = (buf.flags & V4L2_BUF_FLAG_KEYFRAME) != 0
@@ -224,7 +253,8 @@ def thread_poll(self, buf_available):
224253
# Write output to file
225254
b = self.bufs[buf.index][0].read(buf.m.planes[0].bytesused)
226255
self.bufs[buf.index][0].seek(0)
227-
self.outputframe(b, keyframe, (buf.timestamp.secs * 1000000) + buf.timestamp.usecs)
256+
if self._check_for_picture(b):
257+
self.outputframe(b, keyframe, (buf.timestamp.secs * 1000000) + buf.timestamp.usecs)
228258

229259
# Requeue encoded buffer
230260
buf = v4l2_buffer()
@@ -239,10 +269,6 @@ def thread_poll(self, buf_available):
239269
buf.m.planes[0].length = buflen
240270
ret = fcntl.ioctl(self.vd, VIDIOC_QBUF, buf)
241271

242-
# Release frame from camera
243-
queue_item = self.buf_frame.get()
244-
queue_item.release()
245-
246272
def _encode(self, stream, request):
247273
"""Encodes a frame
248274

0 commit comments

Comments
 (0)