Skip to content

Commit

Permalink
Merge branch 'next' into pr-exif-data
Browse files Browse the repository at this point in the history
  • Loading branch information
Amudtogal authored Oct 5, 2023
2 parents 62efbf8 + 14f54a9 commit a3e45a9
Show file tree
Hide file tree
Showing 36 changed files with 1,054 additions and 144 deletions.
2 changes: 2 additions & 0 deletions apps/app_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ def setSingleStep(self, val):

def setValue(self, val, emit=False):
self.blockAllSignals(True)
if val is None:
val = 0
self.box.setValue(val)
self.slider.setValue(int(val / self.precision))
self.blockAllSignals(False)
Expand Down
3 changes: 1 addition & 2 deletions examples/capture_circular.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
picam2.start_preview()
encoder = H264Encoder(1000000, repeat=True)
encoder.output = CircularOutput()
picam2.encoder = encoder
picam2.start()
picam2.start_encoder()
picam2.start_encoder(encoder)

w, h = lsize
prev = None
Expand Down
3 changes: 1 addition & 2 deletions examples/capture_motion.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
lores={"size": lsize, "format": "YUV420"})
picam2.configure(video_config)
encoder = H264Encoder(1000000)
picam2.encoder = encoder
picam2.start()

w, h = lsize
Expand All @@ -32,7 +31,7 @@
if mse > 7:
if not encoding:
encoder.output = FileOutput(f"{int(time.time())}.h264")
picam2.start_encoder()
picam2.start_encoder(encoder)
encoding = True
print("New Motion", mse)
ltime = time.time()
Expand Down
2 changes: 1 addition & 1 deletion examples/capture_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
conn, addr = sock.accept()
stream = conn.makefile("wb")
encoder.output = FileOutput(stream)
picam2.start_encoder()
picam2.start_encoder(encoder)
picam2.start()
time.sleep(20)
picam2.stop()
Expand Down
3 changes: 1 addition & 2 deletions examples/dual_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@
mjpeg_encoder.bitrate = 5000000
mjpeg_encoder.output = FileOutput("out.mjpeg")
mjpeg_encoder.start()
lores_stream = picam2.stream_map["lores"]

picam2.start_recording(h264_encoder, h264_output)

start = time.time()
while time.time() - start < 5:
request = picam2.capture_request()
mjpeg_encoder.encode(lores_stream, request)
mjpeg_encoder.encode("lores", request)
request.release()

mjpeg_encoder.stop()
Expand Down
9 changes: 6 additions & 3 deletions picamera2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@
from .converters import YUV420_to_RGB
from .metadata import Metadata
from .picamera2 import Picamera2, Preview
from .platform import Platform, get_platform
from .request import CompletedRequest, MappedArray
from .sensor_format import SensorFormat

if os.environ.get("XDG_SESSION_TYPE", None) == "wayland":
# The code here works through the X wayland layer, but not otherwise.
os.environ["QT_QPA_PLATFORM"] = "xcb"


def _set_configuration_file(filename):
platform_dir = "vc4" if get_platform() == Platform.VC4 else "pisp"
dirs = [
os.path.expanduser(
"~/libcamera/src/libcamera/pipeline/rpi/vc4/data"
"~/libcamera/src/libcamera/pipeline/rpi/" + platform_dir + "/data"
),
"/usr/local/share/libcamera/pipeline/rpi/vc4",
"/usr/share/libcamera/pipeline/rpi/vc4"]
"/usr/local/share/libcamera/pipeline/rpi/" + platform_dir,
"/usr/share/libcamera/pipeline/rpi/" + platform_dir]

for directory in dirs:
file = os.path.join(directory, filename)
Expand Down
3 changes: 3 additions & 0 deletions picamera2/allocators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .allocator import Allocator
from .dmaallocator import DmaAllocator
from .libcameraallocator import LibcameraAllocator
45 changes: 45 additions & 0 deletions picamera2/allocators/allocator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
class Allocator:
"""Base class for allocators"""

def __init__(self):
self.sync = Sync

def allocate(self, libcamera_config):
pass

def buffers(self, stream):
pass

def acquire(self, bufs):
pass

def release(self, bufs):
pass


class Sync:
"""Base class for allocator syncronisations"""

def __init__(self, allocator, fb, write):
self.__fb = fb

def __enter__(self):
import mmap

# Check if the buffer is contiguous and find the total length.
fd = self.__fb.planes[0].fd
planes_metadata = self.__fb.metadata.planes
buflen = 0
for p, p_metadata in zip(self.__fb.planes, planes_metadata):
# bytes_used is the same as p.length for regular frames, but correctly reflects
# the compressed image size for MJPEG cameras.
buflen = buflen + p_metadata.bytes_used
if fd != p.fd:
raise RuntimeError('_MappedBuffer: Cannot map non-contiguous buffer!')

self.__mm = mmap.mmap(fd, buflen, mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE)
return self.__mm

def __exit__(self, exc_type=None, exc_value=None, exc_traceback=None):
if self.__mm is not None:
self.__mm.close()
117 changes: 117 additions & 0 deletions picamera2/allocators/dmaallocator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import fcntl
import logging
import mmap
import os

import libcamera

from picamera2.allocators.allocator import Allocator, Sync
from picamera2.dma_heap import (DMA_BUF_IOCTL_SYNC, DMA_BUF_SYNC_END,
DMA_BUF_SYNC_READ, DMA_BUF_SYNC_RW,
DMA_BUF_SYNC_START, DmaHeap, dma_buf_sync)

_log = logging.getLogger("picamera2")


class DmaAllocator(Allocator):
"""DmaHeap Allocator"""

def __init__(self):
super().__init__()
self.dmaHeap = DmaHeap()
self.mapped_buffers = {}
self.mapped_buffers_used = {}
self.frame_buffers = {}
self.open_fds = []
self.libcamera_fds = []
self.sync = self.DmaSync

def allocate(self, libcamera_config):
# Delete old buffers
self.libcamera_fds = []
self.cleanup()
# Close our copies of fds
for fd in self.open_fds:
os.close(fd)
self.frame_buffers = {}
self.open_fds = []

for c, stream_config in enumerate(libcamera_config):
stream = stream_config.stream
fb = []
for i in range(stream_config.buffer_count):
fd = self.dmaHeap.alloc(f"picamera2-{i}", stream_config.frame_size)
# Keep track of our allocated fds, as libcamera makes copies
self.open_fds.append(fd.get())

if not fd.isValid():
raise RuntimeError(f"failed to allocate capture buffers for stream {c}")

plane = [libcamera.FrameBuffer.Plane()]
plane[0].fd = fd.get()
plane[0].offset = 0
plane[0].length = stream_config.frame_size

self.libcamera_fds.append(plane[0].fd)
self.mapped_buffers_used[plane[0].fd] = False

fb.append(libcamera.FrameBuffer(plane))
memory = mmap.mmap(plane[0].fd, stream_config.frame_size, mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE)
self.mapped_buffers[fb[-1]] = memory

self.frame_buffers[stream] = fb
msg = f"Allocated {len(fb)} buffers for stream {c} with fds {[f.planes[0].fd for f in self.frame_buffers[stream]]}"
_log.debug(msg)

def buffers(self, stream):
return self.frame_buffers[stream]

def acquire(self, buffers):
for buffer in buffers.values():
fd = buffer.planes[0].fd
self.mapped_buffers_used[fd] = True

def release(self, buffers):
for buffer in buffers.values():
fd = buffer.planes[0].fd
self.mapped_buffers_used[fd] = False
self.cleanup()

def cleanup(self):
for k, v in self.mapped_buffers.items():
fd = k.planes[0].fd
if not self.mapped_buffers_used[fd] and fd not in self.libcamera_fds:
# Not in use by any requests, and not currently allocated
v.close()
del self.mapped_buffers_used[fd]
for k in [k for k, v in self.mapped_buffers.items() if v.closed]:
del self.mapped_buffers[k]

class DmaSync(Sync):
"""Dma Buffer Sync"""

def __init__(self, allocator, fb, write):
self.allocator = allocator
self.__fb = fb
self.__write = write

def __enter__(self):
dma_sync = dma_buf_sync()
dma_sync.flags = DMA_BUF_SYNC_START | (DMA_BUF_SYNC_RW if self.__write else DMA_BUF_SYNC_READ)

it = self.allocator.mapped_buffers.get(self.__fb, None)
if it is None:
raise RuntimeError("failed to find buffer in DmaSync")

ret = fcntl.ioctl(self.__fb.planes[0].fd, DMA_BUF_IOCTL_SYNC, dma_sync)
if ret:
raise RuntimeError("failed to lock-sync-write dma buf")
return it

def __exit__(self, exc_type=None, exc_value=None, exc_traceback=None):
dma_sync = dma_buf_sync()
dma_sync.flags = DMA_BUF_SYNC_END | (DMA_BUF_SYNC_RW if self.__write else DMA_BUF_SYNC_READ)

ret = fcntl.ioctl(self.__fb.planes[0].fd, DMA_BUF_IOCTL_SYNC, dma_sync)
if ret:
logging.error("failed to unlock-sync-write dma buf")
28 changes: 28 additions & 0 deletions picamera2/allocators/libcameraallocator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import logging

import libcamera

from picamera2.allocators.allocator import Allocator

_log = logging.getLogger("picamera2")


class LibcameraAllocator(Allocator):
"""Uses the libcamera FrameBufferAllocator"""

def __init__(self, camera):
super().__init__()
self.camera = camera

def allocate(self, libcamera_config):
self.allocator = libcamera.FrameBufferAllocator(self.camera)
streams = [stream_config.stream for stream_config in libcamera_config]
for i, stream in enumerate(streams):
if self.allocator.allocate(stream) < 0:
logging.critical("Failed to allocate buffers.")
raise RuntimeError("Failed to allocate buffers.")
msg = f"Allocated {len(self.allocator.buffers(stream))} buffers for stream {i}"
_log.debug(msg)

def buffers(self, stream):
return self.allocator.buffers(stream)
2 changes: 1 addition & 1 deletion picamera2/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class StreamConfiguration(Configuration):

class CameraConfiguration(Configuration):
_ALLOWED_FIELDS = ("use_case", "buffer_count", "transform", "display", "encode", "colour_space",
"controls", "main", "lores", "raw", "queue")
"controls", "main", "lores", "raw", "queue", "sensor")
_FIELD_CLASS_MAP = {"main": StreamConfiguration, "lores": StreamConfiguration, "raw": StreamConfiguration}
_FORWARD_FIELDS = {"size": "main", "format": "main"}

Expand Down
Loading

0 comments on commit a3e45a9

Please sign in to comment.