Skip to content

Commit 8a1ecb3

Browse files
committed
Update libav codecs for latest version of PyAV
PyAV now has a from_numpy_buffer method for making a frame from a numpy array that we can use, and which should be more efficient. Signed-off-by: David Plowman <[email protected]>
1 parent b799330 commit 8a1ecb3

File tree

3 files changed

+33
-4
lines changed

3 files changed

+33
-4
lines changed

picamera2/encoders/encoder.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def __init__(self):
6868
self.audio_output = {'codec_name': 'aac'}
6969
self.audio_sync = -100000 # in us, so by default, delay audio by 100ms
7070
self._audio_start = threading.Event()
71+
self.frames_encoded = 0
7172

7273
@property
7374
def running(self):
@@ -240,8 +241,11 @@ def encode(self, stream, request):
240241
self._audio_start.set() # Signal the audio encode thread to start.
241242
if self._skip_count == 0:
242243
with self._lock:
244+
if not self._running:
245+
return
243246
self._encode(stream, request)
244247
self._skip_count = (self._skip_count + 1) % self.frame_skip_count
248+
self.frames_encoded += 1
245249

246250
def _encode(self, stream, request):
247251
if isinstance(stream, str):
@@ -254,6 +258,7 @@ def start(self, quality=None):
254258
with self._lock:
255259
if self._running:
256260
raise RuntimeError("Encoder already running")
261+
self.frames_encoded = 0
257262
self._setup(quality)
258263
self._running = True
259264
self.firsttimestamp = None

picamera2/encoders/libav_h264_encoder.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""This is a base class for a multi-threaded software encoder."""
22

3+
import collections
34
import time
45
from fractions import Fraction
56
from math import sqrt
@@ -30,6 +31,8 @@ def __init__(self, bitrate=None, repeat=True, iperiod=30, framerate=30, qp=None,
3031
self.threads = 0 # means "you choose"
3132
self._lasttimestamp = None
3233
self._use_hw = False
34+
self._request_release_delay = 1
35+
self._request_release_queue = None
3336

3437
@property
3538
def use_hw(self):
@@ -121,6 +124,8 @@ def _start(self):
121124
"XRGB8888": "bgra"}
122125
self._av_input_format = FORMAT_TABLE[self._format]
123126

127+
self._request_release_queue = collections.deque()
128+
124129
def _stop(self):
125130
if not self.drop_final_frames:
126131
# Annoyingly, libav still has lots of encoded frames internally which we must flush
@@ -134,13 +139,19 @@ def _stop(self):
134139
time.sleep(delay_us / 1000000)
135140
self._lasttimestamp = (time.monotonic_ns(), packet.pts)
136141
self.outputframe(bytes(packet), packet.is_keyframe, timestamp=packet.pts, packet=packet)
142+
while self._request_release_queue:
143+
self._request_release_queue.popleft().release()
137144
self._container.close()
138145

139146
def _encode(self, stream, request):
147+
request.acquire()
148+
self._request_release_queue.append(request)
140149
timestamp_us = self._timestamp(request)
141150
with MappedArray(request, stream) as m:
142-
frame = av.VideoFrame.from_ndarray(m.array, format=self._av_input_format, width=self.width)
151+
frame = av.VideoFrame.from_numpy_buffer(m.array, format=self._av_input_format, width=self.width)
143152
frame.pts = timestamp_us
144153
for packet in self._stream.encode(frame):
145154
self._lasttimestamp = (time.monotonic_ns(), packet.pts)
146155
self.outputframe(bytes(packet), packet.is_keyframe, timestamp=packet.pts, packet=packet)
156+
while len(self._request_release_queue) > self._request_release_delay:
157+
self._request_release_queue.popleft().release()

picamera2/encoders/libav_mjpeg_encoder.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""This is a base class for a multi-threaded software encoder."""
22

3+
import collections
34
from fractions import Fraction
45

56
import av
@@ -21,6 +22,8 @@ def __init__(self, bitrate=None, repeat=True, iperiod=30, framerate=30, qp=None)
2122
self.iperiod = iperiod
2223
self.framerate = framerate
2324
self.qp = qp
25+
self._request_release_delay = 1
26+
self._request_release_queue = None
2427

2528
def _setup(self, quality):
2629
# If an explicit quality was specified, use it, otherwise try to preserve any bitrate/qp
@@ -40,7 +43,7 @@ def _start(self):
4043
self._stream = self._container.add_stream(self._codec, rate=self.framerate)
4144

4245
self._stream.codec_context.thread_count = 8
43-
self._stream.codec_context.thread_type = av.codec.context.ThreadType.FRAME
46+
self._stream.codec_context.thread_type = av.codec.context.ThreadType.FRAME # noqa
4447

4548
self._stream.width = self.width
4649
self._stream.height = self.height
@@ -62,7 +65,11 @@ def _start(self):
6265
self._stream.codec_context.qmin = self.qp
6366
self._stream.codec_context.qmax = self.qp
6467
self._stream.codec_context.color_range = 2 # JPEG (full range)
65-
self._stream.codec_context.flags |= av.codec.context.Flags.QSCALE
68+
try:
69+
# "qscale" is now correct, but some older versions used "QSCALE"
70+
self._stream.codec_context.flags |= av.codec.context.Flags.qscale # noqa
71+
except AttributeError:
72+
self._stream.codec_context.flags |= av.codec.context.Flags.QSCALE # noqa
6673

6774
self._stream.codec_context.time_base = Fraction(1, 1000000)
6875

@@ -73,15 +80,21 @@ def _start(self):
7380
"XRGB8888": "bgra"}
7481
self._av_input_format = FORMAT_TABLE[self._format]
7582

83+
self._request_release_queue = collections.deque()
84+
7685
def _stop(self):
7786
for packet in self._stream.encode():
7887
self.outputframe(bytes(packet), packet.is_keyframe, timestamp=packet.pts, packet=packet)
88+
while self._request_release_queue:
89+
self._request_release_queue.popleft().release()
7990
self._container.close()
8091

8192
def _encode(self, stream, request):
8293
timestamp_us = self._timestamp(request)
8394
with MappedArray(request, stream) as m:
84-
frame = av.VideoFrame.from_ndarray(m.array, format=self._av_input_format, width=self.width)
95+
frame = av.VideoFrame.from_numpy_buffer(m.array, format=self._av_input_format, width=self.width)
8596
frame.pts = timestamp_us
8697
for packet in self._stream.encode(frame):
8798
self.outputframe(bytes(packet), packet.is_keyframe, timestamp=packet.pts, packet=packet)
99+
while len(self._request_release_queue) > self._request_release_delay:
100+
self._request_release_queue.popleft().release()

0 commit comments

Comments
 (0)