tiagocoutinho / linuxpy

Human friendly interface to linux subsystems using python
https://tiagocoutinho.github.io/linuxpy/
GNU General Public License v3.0
28 stars 3 forks source link

BrokenPipeError accessing linux.video.Device.controls for an EMEET SmartCam E965 #37

Open mairda opened 2 weeks ago

mairda commented 2 weeks ago

A BrokenPipeError is raised accessing linux.video.Device.controls for an EMEET SmartCam E965 (USB ID 328f:003c). Two other model cameras on the same host have no problem reading/writing control values. The controls sample app shows zero controls for the problem camera using only a --device argument but shows a full list of controls with normal values and rangesfor the two other brand cameras on the host.

mairda commented 2 weeks ago

By accessing the the linuxpy.video.Device object's controls property inside a try/except block in a short loop and handling except BrokenPipeError as a pass/retry it becomes a NameError exception. assumed due to the linuxpy.video.Device.controls dictionary for the camera object being empty, it has the value {}. Again, only for the reported camera make/model not for two other cameras on the same host and a binary executable V4L2 app sees the full set of correct controls for the same camera. I'll try stracing it and see if I can get closer to the cause.

mairda commented 2 weeks ago

Using the control sample program here's t he relevant part from strace -o test.log python3 v4l2py-ctl.py --device /dev/video2:

openat(AT_FDCWD, "/dev/video2", O_RDWR|O_NONBLOCK|O_CLOEXEC) = 3 ioctl(3, FIOCLEX) = 0 fstat(3, {st_mode=S_IFCHR|0660, st_rdev=makedev(0x51, 0x2), ...}) = 0 ioctl(3, VIDIOC_QUERY_EXT_CTRL, {id=V4L2_CTRL_FLAG_NEXT_CTRL|V4L2_CTRL_FLAG_NEXT_COMPOUND|0 / V4L2CID??? / => V4L2_CTRL_CLASS_USER+0x1, type=V4L2_CTRL_TYPE_CTRL_CLASS, name="User Controls", ...}) = 0 ioctl(3, VIDIOC_QUERY_EXT_CTRL, {id=V4L2_CTRL_FLAG_NEXT_CTRL|V4L2_CTRL_FLAG_NEXT_COMPOUND|V4L2_CTRL_CLASS_USER+0x1 => V4L2_CID_BRIGHTNESS, type=V4L2_CTRL_TYPE_INTEGER, name="Brightness", ...}) = 0 ioctl(3, VIDIOC_QUERY_EXT_CTRL, {id=V4L2_CTRL_FLAG_NEXT_CTRL|V4L2_CTRL_FLAG_NEXT_COMPOUND|V4L2_CID_BRIGHTNESS => V4L2_CID_CONTRAST, type=V4L2_CTRL_TYPE_INTEGER, name="Contrast", ...}) = 0 ... ioctl(3, VIDIOC_QUERY_EXT_CTRL, {id=V4L2_CTRL_FLAG_NEXT_CTRL|V4L2_CTRL_FLAG_NEXT_COMPOUND|V4L2_CID_FOCUS_ABSOLUTE => V4L2_CID_FOCUS_AUTO, type=V4L2_CTRL_TYPE_BOOLEAN, name="Focus, Automatic Continuous", ...}) = 0 ioctl(3, VIDIOC_QUERY_EXT_CTRL, {id=V4L2_CTRL_FLAG_NEXT_CTRL|V4L2_CTRL_FLAG_NEXT_COMPOUND|V4L2_CID_FOCUS_AUTO}) = -1 EPIPE (Broken pipe) close(3) = 0

Arguably the fault of the camera's handling of the VIDIOC_QUERY_EXT_CTRL on what I understand is the highest control ID number with no next control. However, all the controls are actually successfully listed and if linuxpy.video consumed exceptions similar to this assuming it's the end of the enumeration then it would retain the previously obtained control IDs and leave them available to the user. I'll try hacking the control enumeration to handle this specific case.

mairda commented 2 weeks ago

I believe this would be a solution:

*** video/device.py     2024-08-27 15:51:21.264206866 -0600
--- video/device_b.py   2024-08-27 15:52:28.624345590 -0600
*************** def iter_read(fd, ioc, indexed_struct, s
*** 191,196 ****
--- 191,199 ----
                      continue
                  else:
                      break
+             elif error.errno == errno.EPIPE:
+                 # Ignore EPIPE
+                 break
              elif error.errno == errno.ENOTTY:
                  # The ioctl is not supported by the driver
                  break
mairda commented 2 weeks ago

I still have problems setting control values but with the above video.Device.controls is no longer empty. The patch is as trivial as possible, I have no problem if it is seen as too simple and needs more care but the point should be obvious in relation to the strace.

tiagocoutinho commented 1 week ago

I have fixed the initial problem following your suggestion. Release 0.16.0 should have the fix. What problems do you still have setting the value? is the control disabled, inactive or read only?

You can check this with something like:

>>> from linuxpy.video.device import Device
>>> d = Device.from_id(0);d.open()
>>> b = d190.controls.brightness
>>> b.is_flagged_disabled
False
>>> b.is_flagged_write_only
False
>>> b.is_flagged_inactive
False
>>> b.is_flagged_read_only
False
mairda commented 1 week ago

Thanks for the fix.

I suspect I can set controls but my problem is I never "get the chance to". The app might not seem an obvious model as it's not a simple video streaming app, you can find an older version on github at: https://github.com/mairda/CSDevs See the csdcapture.py file for my problem area porting it to linuxpy.video, it's a PySide Qt 6.7 thread that is explained below.

The app was written several years ago for v4l2py and is intended to capture a single frame at regular intervals from a webcam, e.g. every minute. These can later be composed into a timelapse video of the day. So the v4l2 usage model was to open the camera at the end of each interval, capture the frame and close the camera until the next interval expires. It can be debated that this risks permitting another program to open the camera exclusively between intervals expiring but put that aside for now. With a webcam you can't just open, streamon, capture the first frame, streamoff and close. The camera has a settling time (particularly for exposure) so you have to capture some nth frame after streamon where n is a number that is camera specific but typical USB webcams settle within about 40 frames of streamon (nth = 37 works for all my webcams). The application also performs it's own exposure control automation, for things like day and night lighting, so can be configured with the identities of controls that adjust brightness, contrast and saturation and range limits can be set for them (or not) for day and/or night. Then the application adjusts them based on the exposure in the previous period's nth captured frame aiming to keep the exposure within user set ranges for brightness, contrast and saturation. So, the application's choice of v4l2 control settings have to be applied after streamon and before the nth frame is reached and captured. For all my webcams this "mth" frame can be the 12th after streamon. This is what I cannot get to work, I never receive any frames so never reach the one where I set the controls. In the v4l2py based code I was setting up the ring buffer myself, using v4l2_requestbuffers for something like 47 MMAP buffers. Initializing them all myself and using IOC.QBUF I then perform IOC.STREAMON and use select() on the camera fd waiting for a frame, dequeue the buffer and immediately requeue if it's not the nth frame to be captured, I'm counting them as I dequeue them. When I reach the mth buffer I apply the application's control changes. When I dequeue the nth frame I save it as the captured frame by reading the buffer. This has some advantages as I don't actually read any buffer other than the nth one (just spin through the buffers counting them and ignoring all but the nth and set the controls on the mth but ignore the buffer). None of that works any longer with linuxpy.video and I never set the camera controls, not that I can't. I probably have to use BufferManager but I haven't figured out how to manage my own choice of buffer size/type then handle dequeue/requeue until there is one I care about. There isn't a BufferManager example that I've found. In my last working v4l2py version I had added motion detection and outlining the motion areas in a frame and could generate nice daily timelapse of the changing scene alongside the fixed schedule timelapse of the scene as-is. It's not intended as a home security app, I wrote it because I like timelapse of a 8 inch deep snow storms in the view from my windows, but it was obvious it was useful for camera based monitoring, especially with the addition of motion detection. I have been trying to port it to linuxpy.video but reached a roadblock at handling frames with the kind of control described. Any input or direction would be welcome.