Skip to content

Commit

Permalink
Update libav codecs for latest version of PyAV
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
davidplowman committed Jan 8, 2025
1 parent b799330 commit 8a1ecb3
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 4 deletions.
5 changes: 5 additions & 0 deletions picamera2/encoders/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand Down
13 changes: 12 additions & 1 deletion picamera2/encoders/libav_h264_encoder.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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()
19 changes: 16 additions & 3 deletions picamera2/encoders/libav_mjpeg_encoder.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""This is a base class for a multi-threaded software encoder."""

import collections
from fractions import Fraction

import av
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)

Expand All @@ -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()

0 comments on commit 8a1ecb3

Please sign in to comment.