Skip to content

Commit

Permalink
Support multi-planar camera capability (Bugfix) (#721)
Browse files Browse the repository at this point in the history
* Fix: support camera multi-planar capabiltiy

Some camera devices only support the multi-planar API through the
Video Capture interface [1][2], therefore, improve this script to
support it.

[1]: https://www.kernel.org/doc/html/v5.15/userspace-api/media/v4l/dev-capture.html#querying-capabilities
[2]: https://www.kernel.org/doc/html/v5.15/userspace-api/media/v4l/vidioc-querycap.html?highlight=vidioc_querycap#description

* Fix: typo and plural

* Modify: refactor detect function and add unit test
  • Loading branch information
baconYao authored Oct 23, 2023
1 parent 77fef62 commit e03c6a5
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 27 deletions.
74 changes: 47 additions & 27 deletions providers/base/bin/camera_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class v4l2_capability(ctypes.Structure):

# Values for 'capabilities' field
V4L2_CAP_VIDEO_CAPTURE = 0x00000001
V4L2_CAP_VIDEO_CAPTURE_MPLANE = 0x00001000
V4L2_CAP_VIDEO_OVERLAY = 0x00000004
V4L2_CAP_READWRITE = 0x01000000
V4L2_CAP_STREAMING = 0x04000000
Expand Down Expand Up @@ -186,35 +187,54 @@ def detect(self):
except IOError:
continue
dev_status = 0
print("%s: OK" % device)
print(" name : %s" % cp.card.decode('UTF-8'))
print(" driver : %s" % cp.driver.decode('UTF-8'))
print(
" version: %s.%s.%s"
% (cp.version >> 16, (cp.version >> 8) & 0xff,
cp.version & 0xff))
print(" flags : 0x%x [" % cp.capabilities,
' CAPTURE' if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE
else '',
' OVERLAY' if cp.capabilities & V4L2_CAP_VIDEO_OVERLAY
else '',
' READWRITE' if cp.capabilities & V4L2_CAP_READWRITE
else '',
' STREAMING' if cp.capabilities & V4L2_CAP_STREAMING
else '',
' ]', sep="")

resolutions = self._supported_resolutions_to_string(
self._get_supported_resolutions(device))
resolutions = resolutions.replace(
"Resolutions:", " Resolutions:")
resolutions = resolutions.replace("Format:", " Format:")
print(resolutions)

if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
cap_status = 0
cap_status = self._detect_and_show_camera_info(device, cp)

return dev_status | cap_status

def _detect_and_show_camera_info(self, device, cp):
"""
Detect the capture capability and show the information of a specific
camera device
:param device:
Full path of camera device under /dev. e.g. /dev/video0
:param cp:
The v4l2 capabitliy
:returns:
0 if the camera supports the capture capability
1 if the camera doesn't support the capture capability
"""
capture_capabilities = cp.capabilities & V4L2_CAP_VIDEO_CAPTURE or \
cp.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE

print("%s: OK" % device)
print(" name : %s" % cp.card.decode('UTF-8'))
print(" driver : %s" % cp.driver.decode('UTF-8'))
print(
" version: %s.%s.%s"
% (cp.version >> 16, (cp.version >> 8) & 0xff,
cp.version & 0xff))
print(" flags : 0x%x [" % cp.capabilities,
' CAPTURE' if capture_capabilities
else '',
' OVERLAY' if cp.capabilities & V4L2_CAP_VIDEO_OVERLAY
else '',
' READWRITE' if cp.capabilities & V4L2_CAP_READWRITE
else '',
' STREAMING' if cp.capabilities & V4L2_CAP_STREAMING
else '',
' ]', sep="")

resolutions = self._supported_resolutions_to_string(
self._get_supported_resolutions(device))
resolutions = resolutions.replace(
"Resolutions:", " Resolutions:")
resolutions = resolutions.replace("Format:", " Format:")
print(resolutions)

return 0 if capture_capabilities else 1

def _stop(self):
self.camerabin.set_state(Gst.State.NULL)
Gtk.main_quit()
Expand Down
114 changes: 114 additions & 0 deletions providers/base/tests/test_camera_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
# Copyright 2023 Canonical Ltd.
# Written by:
# Patrick Chang <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# pylint: disable=protected-access

import unittest
import io
import sys
from unittest.mock import patch

from camera_test import CameraTest, v4l2_capability


class CameraTestTests(unittest.TestCase):
"""This class provides test cases for the CameraTest class."""
def setUp(self):
# supress stdout to hide print message
suppress_text = io.StringIO()
sys.stdout = suppress_text

self.camera_instance = CameraTest(None)

@patch('camera_test.CameraTest._supported_resolutions_to_string')
@patch('camera_test.CameraTest._get_supported_resolutions')
def test_detect_and_show_camera_info_with_single_planar_capture_capability(
self,
mock_get_supported_resolutions,
mock_supported_resolutions_to_string
):
"""Test camera device supports the single planar capture capabilitiy"""
mock_get_supported_resolutions.return_value = [{
'description': 'fake',
'pixelformat': 'fake',
'resolutions': [[123, 987]]
}]
mock_supported_resolutions_to_string.return_value = "Resolutions: fake"

fake_device = '/dev/video0'
fake_v4l2_capability = v4l2_capability()
fake_v4l2_capability.card = b'fake card'
fake_v4l2_capability.driver = b'fake driver'
fake_v4l2_capability.version = 123
fake_v4l2_capability.capabilities = 0x1
result = self.camera_instance._detect_and_show_camera_info(
fake_device, fake_v4l2_capability)
self.assertEqual(0, result)

@patch('camera_test.CameraTest._supported_resolutions_to_string')
@patch('camera_test.CameraTest._get_supported_resolutions')
def test_detect_and_show_camera_info_with_multi_planar_capture_capability(
self,
mock_get_supported_resolutions,
mock_supported_resolutions_to_string
):
"""Test camera device supports the multi planar capture capabilitiy"""
mock_get_supported_resolutions.return_value = [{
'description': 'fake',
'pixelformat': 'fake',
'resolutions': [[123, 987]]
}]
mock_supported_resolutions_to_string.return_value = "Resolutions: fake"

fake_device = '/dev/video0'
fake_v4l2_capability = v4l2_capability()
fake_v4l2_capability.card = b'fake card'
fake_v4l2_capability.driver = b'fake driver'
fake_v4l2_capability.version = 123
fake_v4l2_capability.capabilities = 0x00001000
result = self.camera_instance._detect_and_show_camera_info(
fake_device, fake_v4l2_capability)
self.assertEqual(0, result)

@patch('camera_test.CameraTest._supported_resolutions_to_string')
@patch('camera_test.CameraTest._get_supported_resolutions')
def test_detect_and_show_camera_info_without_capture_capability(
self,
mock_get_supported_resolutions,
mock_supported_resolutions_to_string
):
"""Test camera device doesn't support the capture capabilitiy"""
mock_get_supported_resolutions.return_value = [{
'description': 'YUYV',
'pixelformat': 'YUYV',
'resolutions': [[640, 480]]
}]
mock_supported_resolutions_to_string.return_value = "Resolutions: fake"

fake_device = '/dev/video0'
fake_v4l2_capability = v4l2_capability()
fake_v4l2_capability.card = b'fake card'
fake_v4l2_capability.driver = b'fake driver'
fake_v4l2_capability.version = 123
fake_v4l2_capability.capabilities = 0x000010000
result = self.camera_instance._detect_and_show_camera_info(
fake_device, fake_v4l2_capability)
self.assertEqual(1, result)

def tearDown(self):
# release stdout
sys.stdout = sys.__stdout__

0 comments on commit e03c6a5

Please sign in to comment.