[libcamera-devel] [PATCH v5 04/13] py: cam.py: Use new events support

Laurent Pinchart laurent.pinchart at ideasonboard.com
Wed Jun 7 09:31:24 CEST 2023


Hi Tomi,

On Mon, Jun 05, 2023 at 12:37:26PM +0300, Tomi Valkeinen wrote:
> On 05/06/2023 12:10, Tomi Valkeinen wrote:
> > On 05/06/2023 08:10, Laurent Pinchart wrote:
> >> On Sat, Jun 03, 2023 at 10:56:06AM +0300, Tomi Valkeinen wrote:
> >>> Convert cam.py to use the new event dispatching. In addition to handling
> >>> the request-completed event, handle also disconnect, camera-added and
> >>> camera-removed events (which only do a simple print).
> >>>
> >>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen at ideasonboard.com>
> >>> Reviewed-by: Jacopo Mondi <jacopo at jmondi.org>
> >>> Reviewed-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> >>> ---
> >>>   src/py/cam/cam.py | 27 +++++++++++++++++++++------
> >>>   1 file changed, 21 insertions(+), 6 deletions(-)
> >>>
> >>> diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py
> >>> index a2a115c1..1e2d1f69 100755
> >>> --- a/src/py/cam/cam.py
> >>> +++ b/src/py/cam/cam.py
> >>> @@ -230,11 +230,19 @@ class CaptureState:
> >>>       # Called from renderer when there is a libcamera event
> >>>       def event_handler(self):
> >>>           try:
> >>> -            reqs = self.cm.get_ready_requests()
> >>> -
> >>> -            for req in reqs:
> >>> -                ctx = next(ctx for ctx in self.contexts if ctx.idx == req.cookie)
> >>> -                self.__request_handler(ctx, req)
> >>> +            for ev in self.cm.get_events():
> >>> +                type = ev.type
> >>> +
> >>> +                if type == libcam.Event.Type.CameraAdded:
> >>> +                    print(f'Camera {ev.camera} added')
> >>> +                elif type == libcam.Event.Type.CameraRemoved:
> >>> +                    print(f'Camera {ev.camera} removed')
> >>> +                elif type == libcam.Event.Type.Disconnect:
> >>> +                    print(f'Camera {ev.camera} disconnected')
> >>> +                elif type == libcam.Event.Type.RequestCompleted:
> >>> +                    self.__request_handler(ev.camera, ev.request)
> >>> +                else:
> >>> +                    raise RuntimeError("Bad event type")
> >>
> >> This will cause issues if we later add new event types. Wouldn't it be
> >> better to ignore unknown event types, or possibly print a (one-time)
> >> warning message ?
> > 
> > Right, this error should never happen. Maybe this again shows that we 
> > have some unclear behavior in the bindings. If we make it so that all 
> > the events (including CameraManager's events) must be explicitly 
> > enabled, we can only handle the ones we have enabled, and raise an error 
> > for anything else (or assert). But if we do implicitly enable some 
> > events, we have to ignore any unhandled ones.

Agreed.

> > I'm leaning towards the explicit behavior, as these are somewhat low 
> > level bindings.

Aonther option would be to always emit all events. I think I would
prefer that, an event subscription mechanism seems a bit over-engineered
to me, it's additional complexity for unclear gains. Could we skip it
for now and introduce it later if needed ?

> >>>               running = any(ctx.reqs_completed < ctx.opt_capture for 
> >>> ctx in self.contexts)
> >>>               return running
> >>> @@ -242,7 +250,9 @@ class CaptureState:
> >>>               traceback.print_exc()
> >>>               return False
> >>> -    def __request_handler(self, ctx, req):
> >>> +    def __request_handler(self, cam, req):
> >>> +        ctx = next(ctx for ctx in self.contexts if ctx.camera == cam)
> >>> +
> >>>           if req.status != libcam.Request.Status.Complete:
> >>>               raise Exception('{}: Request failed: {}'.format(ctx.id, req.status))
> >>> @@ -447,6 +457,11 @@ def main():
> >>>           state.do_cmd_capture()
> >>> +        # This is not strictly needed, but it helps to do a proper cleanup as we
> >>> +        # drop any unhandled events, and so makes it easier to use memory leak
> >>> +        # detectors.
> >>> +        cm.get_events()
> >>
> >> Somehow this feels like a hack, outlining a design issue. Is there any
> >> way we could do better ? How about clearing events in the camera manager
> >> destructor ?
> > 
> > If we have events in the queue, the events keep their respective cameras 
> > alive, and the cameras keep the camera manager alive, so there will 
> > never be a destructor call. That said, python gc should detect circular 
> > references, but I'm not sure if it applies here.
> > 
> > But now that I test it, I don't see PyCameraManager destructor being 
> > called at all... So something has gotten broken. Hmm it's probably the 
> > new signal subscription. We keep the requestCompleted subscribed, and we 
> > have passed a shared_ptr<CameraManager> to it...
> 
> Yes, that's the issue. It can be solved by passing a 
> weak_ptr<PyCameraManager> instead. And we also need to disconnect all 
> the signals in the PyCameraManager destructor, as otherwise libcamera 
> complains about "Removing media device /dev/media0 while still in use".

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list