diff --git a/picamera2/encoders/encoder.py b/picamera2/encoders/encoder.py index 456944f6..7a9954fa 100644 --- a/picamera2/encoders/encoder.py +++ b/picamera2/encoders/encoder.py @@ -68,6 +68,7 @@ def __init__(self): self.audio_output = {'codec_name': 'aac'} self.audio_sync = -100000 # in us, so by default, delay audio by 100ms self._audio_start = threading.Event() + self.frames_encoded = 0 @property def running(self): @@ -240,8 +241,11 @@ def encode(self, stream, request): self._audio_start.set() # Signal the audio encode thread to start. if self._skip_count == 0: with self._lock: + if not self._running: + return self._encode(stream, request) self._skip_count = (self._skip_count + 1) % self.frame_skip_count + self.frames_encoded += 1 def _encode(self, stream, request): if isinstance(stream, str): @@ -254,6 +258,7 @@ def start(self, quality=None): with self._lock: if self._running: raise RuntimeError("Encoder already running") + self.frames_encoded = 0 self._setup(quality) self._running = True self.firsttimestamp = None diff --git a/picamera2/encoders/libav_h264_encoder.py b/picamera2/encoders/libav_h264_encoder.py index d47023a7..2825a163 100644 --- a/picamera2/encoders/libav_h264_encoder.py +++ b/picamera2/encoders/libav_h264_encoder.py @@ -1,5 +1,6 @@ """This is a base class for a multi-threaded software encoder.""" +import collections import time from fractions import Fraction from math import sqrt @@ -30,6 +31,8 @@ def __init__(self, bitrate=None, repeat=True, iperiod=30, framerate=30, qp=None, self.threads = 0 # means "you choose" self._lasttimestamp = None self._use_hw = False + self._request_release_delay = 1 + self._request_release_queue = None @property def use_hw(self): @@ -121,6 +124,8 @@ def _start(self): "XRGB8888": "bgra"} self._av_input_format = FORMAT_TABLE[self._format] + self._request_release_queue = collections.deque() + def _stop(self): if not self.drop_final_frames: # Annoyingly, libav still has lots of encoded frames internally which we must flush @@ -134,13 +139,19 @@ def _stop(self): time.sleep(delay_us / 1000000) self._lasttimestamp = (time.monotonic_ns(), packet.pts) self.outputframe(bytes(packet), packet.is_keyframe, timestamp=packet.pts, packet=packet) + while self._request_release_queue: + self._request_release_queue.popleft().release() self._container.close() def _encode(self, stream, request): + request.acquire() + self._request_release_queue.append(request) timestamp_us = self._timestamp(request) with MappedArray(request, stream) as m: - frame = av.VideoFrame.from_ndarray(m.array, format=self._av_input_format, width=self.width) + frame = av.VideoFrame.from_numpy_buffer(m.array, format=self._av_input_format, width=self.width) frame.pts = timestamp_us for packet in self._stream.encode(frame): self._lasttimestamp = (time.monotonic_ns(), packet.pts) self.outputframe(bytes(packet), packet.is_keyframe, timestamp=packet.pts, packet=packet) + while len(self._request_release_queue) > self._request_release_delay: + self._request_release_queue.popleft().release() diff --git a/picamera2/encoders/libav_mjpeg_encoder.py b/picamera2/encoders/libav_mjpeg_encoder.py index 8666a8a4..c6e21c59 100644 --- a/picamera2/encoders/libav_mjpeg_encoder.py +++ b/picamera2/encoders/libav_mjpeg_encoder.py @@ -1,5 +1,6 @@ """This is a base class for a multi-threaded software encoder.""" +import collections from fractions import Fraction import av @@ -21,6 +22,8 @@ def __init__(self, bitrate=None, repeat=True, iperiod=30, framerate=30, qp=None) self.iperiod = iperiod self.framerate = framerate self.qp = qp + self._request_release_delay = 1 + self._request_release_queue = None def _setup(self, quality): # If an explicit quality was specified, use it, otherwise try to preserve any bitrate/qp @@ -40,7 +43,7 @@ def _start(self): self._stream = self._container.add_stream(self._codec, rate=self.framerate) self._stream.codec_context.thread_count = 8 - self._stream.codec_context.thread_type = av.codec.context.ThreadType.FRAME + self._stream.codec_context.thread_type = av.codec.context.ThreadType.FRAME # noqa self._stream.width = self.width self._stream.height = self.height @@ -62,7 +65,11 @@ def _start(self): self._stream.codec_context.qmin = self.qp self._stream.codec_context.qmax = self.qp self._stream.codec_context.color_range = 2 # JPEG (full range) - self._stream.codec_context.flags |= av.codec.context.Flags.QSCALE + try: + # "qscale" is now correct, but some older versions used "QSCALE" + self._stream.codec_context.flags |= av.codec.context.Flags.qscale # noqa + except AttributeError: + self._stream.codec_context.flags |= av.codec.context.Flags.QSCALE # noqa self._stream.codec_context.time_base = Fraction(1, 1000000) @@ -73,15 +80,21 @@ def _start(self): "XRGB8888": "bgra"} self._av_input_format = FORMAT_TABLE[self._format] + self._request_release_queue = collections.deque() + def _stop(self): for packet in self._stream.encode(): self.outputframe(bytes(packet), packet.is_keyframe, timestamp=packet.pts, packet=packet) + while self._request_release_queue: + self._request_release_queue.popleft().release() self._container.close() def _encode(self, stream, request): timestamp_us = self._timestamp(request) with MappedArray(request, stream) as m: - frame = av.VideoFrame.from_ndarray(m.array, format=self._av_input_format, width=self.width) + frame = av.VideoFrame.from_numpy_buffer(m.array, format=self._av_input_format, width=self.width) frame.pts = timestamp_us for packet in self._stream.encode(frame): self.outputframe(bytes(packet), packet.is_keyframe, timestamp=packet.pts, packet=packet) + while len(self._request_release_queue) > self._request_release_delay: + self._request_release_queue.popleft().release()