├── logo.png ├── fmp4streamer.service ├── fmp4streamer.conf.dist ├── h264.py ├── uvcx.py ├── CHANGELOG.md ├── uvcxlogitech.py ├── v4l2ctrls.py ├── v4l2m2m.py ├── LICENSE ├── README.md ├── fmp4streamer.py ├── v4l2camera.py ├── uvcxh264.py ├── bmff.py └── v4l2.py /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyersoyer/fmp4streamer/HEAD/logo.png -------------------------------------------------------------------------------- /fmp4streamer.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=fmp4streamer 3 | After=network.target 4 | [Service] 5 | WorkingDirectory=%h/fmp4streamer 6 | ExecStart=python3 -u fmp4streamer.py 7 | Restart=always 8 | [Install] 9 | WantedBy=default.target 10 | -------------------------------------------------------------------------------- /fmp4streamer.conf.dist: -------------------------------------------------------------------------------- 1 | [server] 2 | listen = 3 | port = 8000 4 | [/dev/video0] 5 | width = 640 6 | height = 480 7 | fps = 30 8 | 9 | # Device capture format (default: H264) 10 | # H264, MJPGH264, YUYV, MJPG, JPEG 11 | # capture_format = H264 12 | 13 | # Decoder M2M device (default: disabled) 14 | # To decode the stream to a compatible format with the encoder (eg MJPG -> NV12 -> H264) 15 | # decoder = /dev/video10 16 | 17 | # Encoder M2M device (default: disabled) 18 | # To encode the stream to H264 (eg YUYV -> H264 or MJPG -> NV12 -> H264) 19 | # encoder = /dev/video11 20 | 21 | # Auto Sleep mode (default: yes) 22 | # Sleep the camera when no one is watching the stream 23 | # auto_sleep = yes 24 | 25 | # Sets the MP4 TRAK rotation matrix (default: 0) 26 | # 0, 90, 180, 270 27 | # rotation = 0 28 | 29 | # Controls 30 | 31 | # you can set any V4L2 control too, list them with the -l option 32 | h264_profile = High 33 | h264_level = 4.2 34 | h264_i_frame_period = 15 35 | 36 | uvcx_h264_i_frame_period = 1000 37 | uvcx_h264_profile = High 38 | 39 | # Advanced (change only if you know, what you are doing) 40 | 41 | # Buffer memory configurations (MMAP or DMABUF) 42 | # Sometimes contigous memory is not large enough for hardwares and tuning needed 43 | 44 | # default: DMABUF if encoder or decoder else MMAP 45 | # capture_memory = DMABUF 46 | 47 | # decoder_memory = MMAP-DMABUF 48 | # encoder_memory = MMAP-MMAP 49 | 50 | # Input format for the decoder (default: MJPG if capture_format == JPEG else capture_format) 51 | # If the capture_format isn't supported by the decoder directly, 52 | # but it can decode it with another format eg (capture_format = JPEG, decoder_input_format = MJPG) 53 | # decoder_input_format = MJPG 54 | 55 | # Input format for the encoder (default: NV12 if decoder else capture_format) 56 | # encoder_input_format = NV12 57 | -------------------------------------------------------------------------------- /h264.py: -------------------------------------------------------------------------------- 1 | import logging, struct, time 2 | from threading import Condition 3 | 4 | class H264NALU: 5 | DELIMITER = b'\x00\x00\x00\x01' 6 | 7 | NONIDRTYPE = 1 8 | IDRTYPE = 5 9 | SPSTYPE = 7 10 | PPSTYPE = 8 11 | 12 | @staticmethod 13 | def get_type(nalubytes): 14 | return nalubytes[0] & 0x1f 15 | 16 | JPEG_SOI = b'\xff\xd8' # JPEG Start Of Image 17 | JPEG_APP4 = b'\xff\xe4' # JPEG APP4 marker to store metadata (H264 frame) 18 | 19 | class H264Parser(object): 20 | def __init__(self): 21 | self.sps = None 22 | self.pps = None 23 | self.condition = Condition() 24 | self.nalus = [] 25 | self.frame_secs = 0 26 | self.frame_usecs = 0 27 | 28 | def write_buf(self, buf): 29 | nalus = [] 30 | 31 | # find H264 inside MJPG 32 | if buf.buffer.find(JPEG_SOI, 0, 2) == 0: 33 | app4_start = buf.buffer.find(JPEG_APP4) 34 | if app4_start != -1: 35 | header_length = struct.unpack_from('> 16) * 4 42 | next_seg_start = app4_start + 65535 43 | while start < end: 44 | next = buf.buffer.find(H264NALU.DELIMITER, start, end) 45 | if next == -1: 46 | next = end 47 | if next > next_seg_start: 48 | nalu = buf.buffer[start : next_seg_start] 49 | while next > next_seg_start: 50 | nalu += buf.buffer[next_seg_start + 4 : min(next, next_seg_start + 65535)] 51 | next_seg_start += 65535 52 | nalus.append(nalu) 53 | else: 54 | nalus.append(memoryview(buf.buffer)[start : next]) 55 | start = next + 4 56 | else: 57 | start = 4 58 | end = buf.bytesused 59 | while start < end: 60 | next = buf.buffer.find(H264NALU.DELIMITER, start, end) 61 | if next == -1: 62 | next = end 63 | nalus.append(memoryview(buf.buffer)[start : next]) 64 | start = next + 4 65 | 66 | if not self.sps or not self.pps: 67 | for nalu in nalus: 68 | if len(nalu) and H264NALU.get_type(nalu) == H264NALU.SPSTYPE: 69 | self.sps = bytes(nalu) 70 | if len(nalu) and H264NALU.get_type(nalu) == H264NALU.PPSTYPE: 71 | self.pps = bytes(nalu) 72 | if not self.sps or not self.pps: 73 | logging.error('H264Parser: Invalid H264 first frame. Unable to read SPS and PPS.') 74 | 75 | if len(nalus) == 0: 76 | logging.warning('H264Parser: 0 NALU found') 77 | return 78 | 79 | with self.condition: 80 | self.nalus = nalus 81 | self.frame_secs = buf.timestamp.secs 82 | self.frame_usecs = buf.timestamp.usecs 83 | self.condition.notify_all() 84 | 85 | # chance for other threads to get the GIL even if the there aren't following blocking reads 86 | # don't have a better solution yet 87 | time.sleep(0.001) 88 | 89 | def read_frame(self): 90 | with self.condition: 91 | self.condition.wait() 92 | return self.nalus, self.frame_secs, self.frame_usecs 93 | -------------------------------------------------------------------------------- /uvcx.py: -------------------------------------------------------------------------------- 1 | import v4l2, ctypes, logging, os.path 2 | from fcntl import ioctl 3 | 4 | # 5 | # ioctl structs, codes for uvc extensions 6 | # 7 | 8 | class uvc_xu_control_query(ctypes.Structure): 9 | _fields_ = [ 10 | ('unit', ctypes.c_uint8), 11 | ('selector', ctypes.c_uint8), 12 | ('query', ctypes.c_uint8), # Video Class-Specific Request Code, 13 | # defined in linux/usb/video.h A.8. 14 | ('size', ctypes.c_uint16), 15 | ('data', ctypes.c_void_p), 16 | ] 17 | 18 | UVCIOC_CTRL_QUERY = v4l2._IOWR('u', 0x21, uvc_xu_control_query) 19 | 20 | # A.8. Video Class-Specific Request Codes 21 | UVC_RC_UNDEFINED = 0x00 22 | UVC_SET_CUR = 0x01 23 | UVC_GET_CUR = 0x81 24 | UVC_GET_MIN = 0x82 25 | UVC_GET_MAX = 0x83 26 | UVC_GET_RES = 0x84 27 | UVC_GET_LEN = 0x85 28 | UVC_GET_INFO = 0x86 29 | UVC_GET_DEF = 0x87 30 | 31 | 32 | def to_buf(b): 33 | return ctypes.create_string_buffer(b) 34 | 35 | def get_length_xu_control(fd, unit_id, selector): 36 | length = ctypes.c_uint16(0) 37 | 38 | xu_ctrl_query = uvc_xu_control_query() 39 | xu_ctrl_query.unit = unit_id 40 | xu_ctrl_query.selector = selector 41 | xu_ctrl_query.query = UVC_GET_LEN 42 | xu_ctrl_query.size = 2 # sizeof(length) 43 | xu_ctrl_query.data = ctypes.cast(ctypes.pointer(length), ctypes.c_void_p) 44 | 45 | try: 46 | ioctl(fd, UVCIOC_CTRL_QUERY, xu_ctrl_query) 47 | except Exception as e: 48 | logging.warning(f'uvcx: UVCIOC_CTRL_QUERY (GET_LEN) - Fd: {fd} - Error: {e}') 49 | 50 | return length 51 | 52 | def query_xu_control(fd, unit_id, selector, query, data): 53 | len = get_length_xu_control(fd, unit_id, selector) 54 | 55 | xu_ctrl_query = uvc_xu_control_query() 56 | xu_ctrl_query.unit = unit_id 57 | xu_ctrl_query.selector = selector 58 | xu_ctrl_query.query = query 59 | xu_ctrl_query.size = len 60 | xu_ctrl_query.data = ctypes.cast(ctypes.pointer(data), ctypes.c_void_p) 61 | 62 | try: 63 | ioctl(fd, UVCIOC_CTRL_QUERY, xu_ctrl_query) 64 | except Exception as e: 65 | logging.warning(f'uvcx: UVCIOC_CTRL_QUERY ({query}) - Fd: {fd} - Error: {e}') 66 | 67 | 68 | # the usb device descriptors file contains the descriptors in a binary format 69 | # the byte before the extension guid is the extension unit id 70 | def find_unit_id_in_sysfs(device, guid): 71 | if os.path.islink(device): 72 | device = os.readlink(device) 73 | device = os.path.basename(device) 74 | descfile = f'/sys/class/video4linux/{device}/../../../descriptors' 75 | if not os.path.isfile(descfile): 76 | return 0 77 | 78 | try: 79 | with open(descfile, 'rb') as f: 80 | descriptors = f.read() 81 | guid_start = descriptors.find(guid) 82 | if guid_start > 0: 83 | return descriptors[guid_start - 1] 84 | except Exception as e: 85 | logging.warning(f'uvcx: failed to read uvc xu unit id from {descfile}: {e}') 86 | 87 | return 0 88 | 89 | def find_usb_ids_in_sysfs(device): 90 | if os.path.islink(device): 91 | device = os.readlink(device) 92 | device = os.path.basename(device) 93 | vendorfile = f'/sys/class/video4linux/{device}/../../../idVendor' 94 | productfile = f'/sys/class/video4linux/{device}/../../../idProduct' 95 | if not os.path.isfile(vendorfile) or not os.path.isfile(productfile): 96 | return '' 97 | 98 | vendor = read_usb_id_from_file(vendorfile) 99 | product = read_usb_id_from_file(productfile) 100 | 101 | return vendor + ':' + product 102 | 103 | def read_usb_id_from_file(file): 104 | id = '' 105 | try: 106 | with open(file, 'r') as f: 107 | id = f.read().strip() 108 | except Exception as e: 109 | logging.warning(f'uvcx: failed to read usb id from {file}: {e}') 110 | return id 111 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## [3.4.7] - 2022-07-19 3 | ### Fixed 4 | - H264Ctrls: refresh_ctrls: check if it is supported 5 | 6 | ## [3.4.6] - 2022-07-19 7 | ### Added 8 | - MP4 rotation 9 | - Ability to set process priority 10 | - Logitech LED controls without uvcdynctrl 11 | ### Fixed 12 | - UVCX: Handle if the device is a symlink (like: /dev/v4l/by-id/...) 13 | - Handle V4L2 buffers which are not in order to support BRIO 14 | - Reduce frame drops 15 | - MJPGH264: 1080p streaming 16 | - Fix for H264 capable C922-s (before 2020) 17 | 18 | ## [3.4.5] - 2022-03-02 19 | ### Added 20 | - New UVC H264 XU controls: uvcx_h264_slice_mode, uvcx_h264_slice_units, uvcx_h264_entropy, uvcx_h264_usage, uvcx_h264_leaky_bucket_size 21 | ### Fixed 22 | - Fixed the glitches if the H264 payload in the MJPG is bigger than 65k 23 | 24 | 25 | ## [3.4.4] - 2022-02-20 26 | ### Changed 27 | - Use TCP_NODELAY to disable Nagle's algorithm for lower latency 28 | - Set viewport for mobiles 29 | 30 | ## [3.4.3] - 2022-02-20 31 | ### Added 32 | - Logo, Favicon, App icon (add to home screen) 33 | ### Fixed 34 | - Remove stacktrace from logging 35 | 36 | ## [3.4.2] - 2022-02-19 37 | ### Fixed 38 | - Fix logging to systemd journal (python -u -> unbuffered stdout) 39 | - Fix systemd service if user is not pi 40 | - Better systemd handling section in README 41 | 42 | ## [3.4.1] - 2022-02-18 43 | ### Fixed 44 | - Basic checks for buffer configurations 45 | 46 | ## [3.4.0] - 2022-02-17 47 | ### Added 48 | - Auto sleep mode (sleep the camera when no one is watching the stream) (enabled by default) 49 | 50 | ## [3.3.2] - 2022-02-17 51 | ### Fixed 52 | - Fix the bug in resolution check caused invalid resolutions could be set 53 | - Use 640x480 in the default config for better compatibility 54 | - Use NV12 instead of YU12 by default for the decoder output, it has the same alignment on the encoder and decoder 55 | 56 | ## [3.3.1] - 2022-02-17 57 | ### Fixed 58 | - Set the correct sizeimage in the m2m decoder for suitable DMABUFs for the camera 59 | 60 | ## [3.3.0] - 2022-02-16 61 | ### Added 62 | - Able to convert MJPG camera stream to H264 via M2M decoder and encoder devices. 63 | ### Fixed 64 | - Don't start the http server until the first H264 frame arrives 65 | 66 | ## [3.2.2] - 2022-02-13 67 | ### Added 68 | - List controls: print device, driver names too 69 | 70 | ## [3.2.1] - 2022-02-13 71 | ### Fixed 72 | - Use a better way to check if the H264 UVC extension exists. Read the ID from the usb descriptors. 73 | 74 | ## [3.2.0] - 2022-02-10 75 | ### Added 76 | - Support UVC H264 extension 77 | - Support H264 inside MJPG 78 | ### Fixed 79 | - Handle H264 slices 80 | 81 | ## [3.1.0] - 2022-01-12 82 | ### Added 83 | - V4L2 M2M H264 encoding for USB cameras 84 | 85 | ## [3.0.3] - 2021-10-25 86 | ### Fixed 87 | - Eliminate the rounding errors in sampleduration calculation 88 | 89 | ## [3.0.2] - 2021-10-24 90 | ### Fixed 91 | - Better sampleduration calculation 92 | ### Added 93 | - Requesting keyframe at new connections for faster startup 94 | 95 | ## [3.0.1] - 2021-10-23 96 | ### Fixed 97 | - Fixed sampleduration calculation 98 | - Default config shouldn't set h264 profile and level 99 | 100 | ## [3.0.0] - 2021-10-22 101 | ### Added 102 | - Use V4L2 instead of the raspivid, because reading from pipe is slow with big frames 103 | - Config file with ability to set v4l2 parameters 104 | ### Changed 105 | - RaspiWebcam has been renamed fmp4streamer, because with V4L2 it works on other linux machines too 106 | 107 | ## [2.2.1] - 2021-10-13 108 | ### Changed 109 | - Better duration and decode time calculation 110 | - Code, speed and compatibility improvements 111 | 112 | ## [2.2.0] - 2021-10-10 113 | ### Added 114 | - HLS streaming for iPhone 115 | ### Changed 116 | - Speed and compatibility improvements 117 | 118 | ## [2.1.4] - 2021-10-09 119 | ### Fixed 120 | - Fixed streaming in Chromium and Android 121 | 122 | ## [2.1.3] - 2021-10-09 123 | ### Changed 124 | - Inline is not needed anymore in the raspivid's parameters 125 | - Speed improvements 126 | 127 | ## [2.1.2] - 2021-10-09 128 | ### Changed 129 | - Works with high fps settings too: use actual decode times for frames, not the fps calculated 130 | ### Fixed 131 | - Fix race condition in MP4Writer 132 | 133 | ## [2.1.1] - 2021-10-09 134 | ### Changed 135 | - Speed up frame writing by 4x 136 | 137 | ## [2.1.0] - 2021-10-08 138 | ### Added 139 | - Use a tiny python muxer instead of the javascript based jmuxer. 140 | - Use html5 video tag instead of Fetch api 141 | ### Changed 142 | - Use correct timescale 143 | 144 | ## [2.0.0] - 2021-10-06 145 | 146 | - Starting a new project based on [dans98/pi-h264-to-browser](https://github.com/dans98/pi-h264-to-browser). 147 | - Raspiwebcam is the new project name. 148 | 149 | ### Added 150 | - Use raspivid instead of python3-picamera 151 | - Use Fetch api instead of websockets 152 | ### Changed 153 | - Use jmuxer 2.0.2 154 | ### Removed 155 | - Removed unnecessary screens 156 | - Removed python3-picamera dependency 157 | - Removed python3-tornado dependency 158 | 159 | -------------------------------------------------------------------------------- /uvcxlogitech.py: -------------------------------------------------------------------------------- 1 | import uvcx, logging 2 | 3 | # Logitech peripheral GUID ffe52d21-8030-4e2c-82d9-f587d00540bd 4 | LOGITECH_PERIPHERAL_GUID = b'\x21\x2d\xe5\xff\x30\x80\x2c\x4e\x82\xd9\xf5\x87\xd0\x05\x40\xbd' 5 | 6 | LOGITECH_PERIPHERAL_LED1_SEL = 0x09 7 | LOGITECH_PERIPHERAL_LED1_LEN = 5 8 | 9 | LOGITECH_PERIPHERAL_LED1_MODE_OFFSET = 1 10 | LOGITECH_PERIPHERAL_LED1_MODE_OFF = 0x00 11 | LOGITECH_PERIPHERAL_LED1_MODE_ON = 0x01 12 | LOGITECH_PERIPHERAL_LED1_MODE_BLINK = 0x02 13 | LOGITECH_PERIPHERAL_LED1_MODE_AUTO = 0x03 14 | 15 | LOGITECH_PERIPHERAL_LED1_FREQUENCY_OFFSET = 3 16 | 17 | class LogitechCtrls: 18 | def __init__(self, device, fd): 19 | self.device = device 20 | self.fd = fd 21 | self.unit_id = uvcx.find_unit_id_in_sysfs(device, LOGITECH_PERIPHERAL_GUID) 22 | 23 | self.get_device_controls() 24 | 25 | def supported(self): 26 | return self.unit_id != 0 27 | 28 | def get_device_controls(self): 29 | if not self.supported(): 30 | self.ctrls = [] 31 | return 32 | 33 | self.ctrls = [ 34 | Ctrl( 35 | 'uvcx_logitech_led1_mode', 36 | LOGITECH_PERIPHERAL_LED1_SEL, 37 | LOGITECH_PERIPHERAL_LED1_LEN, 38 | LOGITECH_PERIPHERAL_LED1_MODE_OFFSET, 39 | { 40 | LOGITECH_PERIPHERAL_LED1_MODE_OFF: 'Off', 41 | LOGITECH_PERIPHERAL_LED1_MODE_ON: 'On', 42 | LOGITECH_PERIPHERAL_LED1_MODE_BLINK: 'Blink', 43 | LOGITECH_PERIPHERAL_LED1_MODE_AUTO: 'Auto' 44 | } 45 | ), 46 | Ctrl( 47 | 'uvcx_logitech_led1_frequency', 48 | LOGITECH_PERIPHERAL_LED1_SEL, 49 | LOGITECH_PERIPHERAL_LED1_LEN, 50 | LOGITECH_PERIPHERAL_LED1_FREQUENCY_OFFSET, 51 | ), 52 | ] 53 | 54 | def print_ctrls(self): 55 | for c in self.ctrls: 56 | 57 | default_config = uvcx.to_buf(bytes(c.len)) 58 | minimum_config = uvcx.to_buf(bytes(c.len)) 59 | maximum_config = uvcx.to_buf(bytes(c.len)) 60 | current_config = uvcx.to_buf(bytes(c.len)) 61 | 62 | uvcx.query_xu_control(self.fd, self.unit_id, c.selector, uvcx.UVC_GET_DEF, default_config) 63 | uvcx.query_xu_control(self.fd, self.unit_id, c.selector, uvcx.UVC_GET_MIN, minimum_config) 64 | uvcx.query_xu_control(self.fd, self.unit_id, c.selector, uvcx.UVC_GET_MAX, maximum_config) 65 | uvcx.query_xu_control(self.fd, self.unit_id, c.selector, uvcx.UVC_GET_CUR, current_config) 66 | 67 | default = default_config[c.offset][0] 68 | minimum = minimum_config[c.offset][0] 69 | maximum = maximum_config[c.offset][0] 70 | value = current_config[c.offset][0] 71 | 72 | print(c.name, end = ' = ') 73 | if not c.menu: 74 | print('%a\t(' % value, 'default:', default, 'min:', minimum, 'max:', maximum, end = ')\n') 75 | else: 76 | valmenu = c.menu.get(value) 77 | defmenu = c.menu.get(default) 78 | print(f'{valmenu if valmenu else value}\t(', end = ' ') 79 | if defmenu: 80 | print(f'default: {defmenu}', end = ' ') 81 | print('values:', end = ' ') 82 | for k, v in c.menu.items(): 83 | print('%a' % v, end = ' ') 84 | print(')') 85 | 86 | def setup_ctrls(self, params): 87 | if not self.supported(): 88 | return 89 | 90 | for k, v in params.items(): 91 | if not k.startswith('uvcx_logitech_'): 92 | continue 93 | ctrl = find_by_name(self.ctrls, k) 94 | if ctrl == None: 95 | logging.warning(f'LogitechCtrls: can\'t find {k} control in {[c.name for c in self.ctrls]}') 96 | continue 97 | if ctrl.menu: 98 | menukey = find_by_value(ctrl.menu, v) 99 | if menukey == None: 100 | logging.warning(f'LogitechCtrls: can\'t find {v} in {list(ctrl.menu.values())}') 101 | continue 102 | desired = menukey 103 | else: 104 | desired = int(v) 105 | 106 | current_config = uvcx.to_buf(bytes(ctrl.len)) 107 | uvcx.query_xu_control(self.fd, self.unit_id, ctrl.selector, uvcx.UVC_GET_CUR, current_config) 108 | current_config[ctrl.offset] = desired 109 | uvcx.query_xu_control(self.fd, self.unit_id, ctrl.selector, uvcx.UVC_SET_CUR, current_config) 110 | uvcx.query_xu_control(self.fd, self.unit_id, ctrl.selector, uvcx.UVC_GET_CUR, current_config) 111 | current = current_config[ctrl.offset][0] 112 | 113 | if ctrl.menu: 114 | desired = ctrl.menu.get(desired, desired) 115 | current = ctrl.menu.get(current, current) 116 | if current != desired: 117 | logging.warning(f'LogitechCtrls: failed to set {k} to {desired}, current value {current}\n') 118 | 119 | 120 | 121 | class Ctrl: 122 | def __init__(self, name, selector, len, offset, menu = None): 123 | self.name = name 124 | self.selector = selector 125 | self.len = len 126 | self.offset = offset 127 | self.menu = menu 128 | 129 | def find_by_name(ctrls, name): 130 | for c in ctrls: 131 | if name == c.name: 132 | return c 133 | return None 134 | 135 | def find_by_value(menu, value): 136 | for k, v in menu.items(): 137 | if v == value: 138 | return k 139 | return None 140 | -------------------------------------------------------------------------------- /v4l2ctrls.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from fcntl import ioctl 3 | 4 | import v4l2 5 | 6 | class V4L2Ctrls: 7 | def __init__(self, device, fd): 8 | self.device = device 9 | self.fd = fd 10 | self.get_device_cap() 11 | self.get_device_controls() 12 | 13 | 14 | def setup_v4l2_ctrls(self, params): 15 | for k, v in params.items(): 16 | if k in ['width', 'height', 'fps', 'auto_sleep', 'rotation', 17 | 'capture_format', 'capture_memory', 18 | 'decoder', 'decoder_input_format', 'decoder_memory', 19 | 'encoder', 'encoder_input_format', 'encoder_memory', 20 | ] or k.startswith('uvcx_'): 21 | continue 22 | ctrl = find_by_name(self.ctrls, k) 23 | if ctrl == None: 24 | logging.warning(f'Can\'t find {k} v4l2 control') 25 | continue 26 | intvalue = 0 27 | if ctrl.type == v4l2.V4L2_CTRL_TYPE_INTEGER: 28 | intvalue = int(v) 29 | elif ctrl.type == v4l2.V4L2_CTRL_TYPE_BOOLEAN: 30 | intvalue = int(bool(v)) 31 | elif ctrl.type == v4l2.V4L2_CTRL_TYPE_MENU: 32 | menu = find_by_name(ctrl.menus, v) 33 | if menu == None: 34 | logging.warning(f'Can\'t find {v} in {[str(c.name, "utf-8") for c in ctrl.menus]}') 35 | continue 36 | intvalue = menu.index 37 | elif ctrl.type == v4l2.V4L2_CTRL_TYPE_INTEGER_MENU: 38 | menu = find_by_value(ctrl.menus, int(v)) 39 | if menu == None: 40 | logging.warning(f'Can\'t find {v} in {[c.value for c in ctrl.menus]}') 41 | continue 42 | intvalue = menu.index 43 | else: 44 | logging.warning(f'Can\'t set {k} to {v} (Unsupported control type {ctrl.type})') 45 | continue 46 | try: 47 | new_ctrl = v4l2.v4l2_control(ctrl.id, intvalue) 48 | ioctl(self.fd, v4l2.VIDIOC_S_CTRL, new_ctrl) 49 | if new_ctrl.value != intvalue: 50 | logging.warning(f'Can\'t set {k} to {v} using {new_ctrl.value} instead of {intvalue}') 51 | continue 52 | ctrl.value = intvalue 53 | except Exception as e: 54 | logging.warning(f'Can\'t set {k} to {v} ({e})') 55 | 56 | 57 | def get_device_cap(self): 58 | cap = v4l2.v4l2_capability() 59 | try: 60 | ioctl(self.fd, v4l2.VIDIOC_QUERYCAP, cap) 61 | except Exception as e: 62 | logging.warning(f'v4l2ctrls: VIDIOC_QUERYCAP failed: ({e})') 63 | 64 | self.card = str(cap.card, 'utf-8') 65 | self.driver = str(cap.driver, 'utf-8') 66 | 67 | def get_device_controls(self): 68 | ctrls = [] 69 | strtrans = bytes.maketrans(b' -', b'__') 70 | next_fl = v4l2.V4L2_CTRL_FLAG_NEXT_CTRL | v4l2.V4L2_CTRL_FLAG_NEXT_COMPOUND 71 | qctrl = v4l2.v4l2_queryctrl(next_fl) 72 | while True: 73 | try: 74 | ioctl(self.fd, v4l2.VIDIOC_QUERYCTRL, qctrl) 75 | except: 76 | break 77 | if qctrl.type in [v4l2.V4L2_CTRL_TYPE_INTEGER, v4l2.V4L2_CTRL_TYPE_BOOLEAN, 78 | v4l2.V4L2_CTRL_TYPE_MENU,v4l2.V4L2_CTRL_TYPE_INTEGER_MENU]: 79 | 80 | try: 81 | ctrl = v4l2.v4l2_control(qctrl.id) 82 | ioctl(self.fd, v4l2.VIDIOC_G_CTRL, ctrl) 83 | qctrl.value = ctrl.value 84 | except: 85 | logging.warning(f'Can\'t get ctrl {qctrl.name} value') 86 | 87 | qctrl.name = qctrl.name.lower().translate(strtrans, delete = b',&(.)').replace(b'__', b'_') 88 | if qctrl.type in [v4l2.V4L2_CTRL_TYPE_MENU, v4l2.V4L2_CTRL_TYPE_INTEGER_MENU]: 89 | qctrl.menus = [] 90 | for i in range(qctrl.minimum, qctrl.maximum + 1): 91 | try: 92 | qmenu = v4l2.v4l2_querymenu(qctrl.id, i) 93 | ioctl(self.fd, v4l2.VIDIOC_QUERYMENU, qmenu) 94 | except: 95 | continue 96 | qctrl.menus.append(qmenu) 97 | 98 | ctrls.append(qctrl) 99 | qctrl = v4l2.v4l2_queryctrl(qctrl.id | next_fl) 100 | 101 | self.ctrls = ctrls 102 | 103 | def print_ctrls(self): 104 | print(f'Device: {self.device}') 105 | print(f'Name: {self.card}') 106 | print(f'Driver: {self.driver}') 107 | print(f'\nControls') 108 | for c in self.ctrls: 109 | if c.type == v4l2.V4L2_CTRL_TYPE_CTRL_CLASS: 110 | print('\n' + str(c.name, 'utf-8')+'\n') 111 | else: 112 | print(str(c.name, 'utf-8'), end = ' = ') 113 | if c.type in [v4l2.V4L2_CTRL_TYPE_MENU, v4l2.V4L2_CTRL_TYPE_INTEGER_MENU]: 114 | defmenu = None 115 | valmenu = None 116 | for m in c.menus: 117 | if m.index == c.value: 118 | valmenu = m 119 | if m.index == c.default: 120 | defmenu = m 121 | if valmenu: 122 | print(f'{str(valmenu.name, "utf-8") if c.type == v4l2.V4L2_CTRL_TYPE_MENU else valmenu.value}\t(', end = ' ') 123 | if defmenu: 124 | print(f'default: {str(defmenu.name, "utf-8") if c.type == v4l2.V4L2_CTRL_TYPE_MENU else defmenu.value}', end = ' ') 125 | print('values:', end = ' ') 126 | for m in c.menus: 127 | print('%a' % (str(m.name, 'utf-8') if c.type == v4l2.V4L2_CTRL_TYPE_MENU else m.value), 128 | end = ' ') 129 | print(')') 130 | elif c.type in [v4l2.V4L2_CTRL_TYPE_INTEGER, v4l2.V4L2_CTRL_TYPE_BOOLEAN]: 131 | print('%a\t(' % c.value, 'default:', c.default, 'min:', c.minimum, 'max:', c.maximum, end = '') 132 | if c.step != 1: 133 | print(' step:', c.step, end = '') 134 | print(')') 135 | else: 136 | print() 137 | 138 | def request_key_frame(self): 139 | try: 140 | ctrl = v4l2.v4l2_control(v4l2.V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, 0) 141 | ioctl(self.fd, v4l2.VIDIOC_S_CTRL, ctrl) 142 | except: 143 | logging.warning(f'{self.device} can\'t request keyframe') 144 | 145 | 146 | def find_by_name(ctrls, name): 147 | for c in ctrls: 148 | if name == str(c.name, 'utf-8'): 149 | return c 150 | return None 151 | 152 | def find_by_value(menus, value): 153 | for m in menus: 154 | if value == m.value: 155 | return m 156 | return None 157 | -------------------------------------------------------------------------------- /v4l2m2m.py: -------------------------------------------------------------------------------- 1 | from fcntl import ioctl 2 | from threading import Thread 3 | import mmap, os, struct, logging, sys 4 | 5 | from v4l2ctrls import V4L2Ctrls 6 | import v4l2 7 | 8 | JPEG_APP0 = b'\xff\xe0' 9 | JPEG_APP4 = b'\xff\xe4' 10 | JPEG_APP4_END = b'\xe4' 11 | 12 | class V4L2M2M(Thread): 13 | def __init__(self, device, pipe, params, width, height, 14 | input_format, capture_format, memory_config, input_sizeimage = 0): 15 | super(V4L2M2M, self).__init__() 16 | 17 | self.device = device 18 | self.stopped = False 19 | self.pipe = pipe 20 | self.num_input_bufs = 6 21 | self.num_cap_bufs = 6 22 | self.input_bufs = [] 23 | self.cap_bufs = [] 24 | 25 | self.input_format = input_format 26 | 27 | self.fd = os.open(self.device, os.O_RDWR, 0) 28 | 29 | self.ctrls = V4L2Ctrls(self.device, self.fd) 30 | self.ctrls.setup_v4l2_ctrls(params) 31 | 32 | [input_memory, capture_memory] = memory_config.split('-') 33 | 34 | self.init_device( 35 | width, 36 | height, 37 | input_format, 38 | capture_format, 39 | input_memory, 40 | capture_memory, 41 | input_sizeimage, 42 | ) 43 | 44 | def init_device(self, width, height, input_format, capture_format, input_memory, capture_memory, input_sizeimage): 45 | 46 | input_pix_fmt = v4l2.get_fourcc(input_format) 47 | out_fmt = v4l2.v4l2_format() 48 | out_fmt.type = v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE 49 | out_fmt.fmt.pix_mp.width = width 50 | out_fmt.fmt.pix_mp.height = height 51 | out_fmt.fmt.pix_mp.pixelformat = input_pix_fmt 52 | out_fmt.fmt.pix_mp.field = v4l2.V4L2_FIELD_ANY 53 | out_fmt.fmt.pix_mp.plane_fmt[0].sizeimage = input_sizeimage 54 | # libcamera currently has no means to request the right colour space, hence: 55 | # fmt.fmt.pix_mp.colorspace = v4l2.V4L2_COLORSPACE_JPEG 56 | ioctl(self.fd, v4l2.VIDIOC_S_FMT, out_fmt) 57 | 58 | if out_fmt.fmt.pix_mp.pixelformat != input_pix_fmt: 59 | logging.error(f'{self.device}: {input_format} input format not available') 60 | sys.exit(3) 61 | 62 | if out_fmt.fmt.pix_mp.width != width or out_fmt.fmt.pix_mp.height != height: 63 | logging.error(f'{self.device}: {width}x{height} input resolution not available') 64 | sys.exit(3) 65 | 66 | capture_pix_fmt = v4l2.get_fourcc(capture_format) 67 | cap_fmt = v4l2.v4l2_format() 68 | cap_fmt.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 69 | cap_fmt.fmt.pix_mp.width = width 70 | cap_fmt.fmt.pix_mp.height = height 71 | cap_fmt.fmt.pix_mp.pixelformat = capture_pix_fmt 72 | cap_fmt.fmt.pix_mp.field = v4l2.V4L2_FIELD_ANY 73 | ioctl(self.fd, v4l2.VIDIOC_S_FMT, cap_fmt) 74 | 75 | if cap_fmt.fmt.pix_mp.pixelformat != capture_pix_fmt: 76 | logging.error(f'{self.device}: {capture_format} capture format not available') 77 | sys.exit(3) 78 | 79 | if cap_fmt.fmt.pix_mp.width != width or cap_fmt.fmt.pix_mp.height != height: 80 | logging.error(f'{self.device}: {width}x{height} capture resolution not available') 81 | sys.exit(3) 82 | 83 | # Request that the necessary buffers are allocated. The output queue 84 | # (input to the encoder) shares buffers from our caller, these must be 85 | # DMABUFs (or MMAP?). Buffers for the encoded bitstream must be allocated and 86 | # m-mapped. 87 | 88 | reqbufs = v4l2.v4l2_requestbuffers() 89 | reqbufs.count = self.num_input_bufs 90 | reqbufs.type = v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE 91 | reqbufs.memory = v4l2.get_mem_type(input_memory) 92 | ioctl(self.fd, v4l2.VIDIOC_REQBUFS, reqbufs) 93 | self.num_input_bufs = reqbufs.count 94 | 95 | for i in range(self.num_input_bufs): 96 | planes = v4l2.v4l2_planes() 97 | buf = v4l2.v4l2_buffer() 98 | buf.type = v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE 99 | buf.memory = reqbufs.memory 100 | buf.index = i 101 | buf.length = 1 102 | buf.m.planes = planes 103 | 104 | ioctl(self.fd, v4l2.VIDIOC_QUERYBUF, buf) 105 | 106 | if buf.memory == v4l2.V4L2_MEMORY_MMAP: 107 | buf.buffer = mmap.mmap(self.fd, buf.m.planes[0].length, 108 | flags=mmap.MAP_SHARED, 109 | prot=mmap.PROT_READ | mmap.PROT_WRITE, 110 | offset=buf.m.planes[0].m.mem_offset) 111 | 112 | expbuf = v4l2.v4l2_exportbuffer() 113 | expbuf.type = buf.type 114 | expbuf.index = buf.index 115 | expbuf.plane = 0 116 | expbuf.flags = os.O_CLOEXEC | os.O_RDWR 117 | 118 | ioctl(self.fd, v4l2.VIDIOC_EXPBUF, expbuf) 119 | buf.fd = expbuf.fd 120 | 121 | self.input_bufs.append(buf) 122 | 123 | 124 | reqbufs = v4l2.v4l2_requestbuffers() 125 | reqbufs.count = self.num_cap_bufs 126 | reqbufs.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 127 | reqbufs.memory = v4l2.get_mem_type(capture_memory) 128 | 129 | ioctl(self.fd, v4l2.VIDIOC_REQBUFS, reqbufs) 130 | self.num_cap_bufs = reqbufs.count 131 | 132 | for i in range(self.num_cap_bufs): 133 | planes = v4l2.v4l2_planes() 134 | buf = v4l2.v4l2_buffer() 135 | buf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 136 | buf.memory = reqbufs.memory 137 | buf.index = i 138 | buf.length = 1 139 | buf.m.planes = planes 140 | 141 | ioctl(self.fd, v4l2.VIDIOC_QUERYBUF, buf) 142 | 143 | if reqbufs.memory == v4l2.V4L2_MEMORY_MMAP: 144 | buf.buffer = mmap.mmap(self.fd, buf.m.planes[0].length, 145 | flags=mmap.MAP_SHARED, 146 | prot=mmap.PROT_READ | mmap.PROT_WRITE, 147 | offset=buf.m.planes[0].m.mem_offset) 148 | 149 | expbuf = v4l2.v4l2_exportbuffer() 150 | expbuf.type = buf.type 151 | expbuf.index = buf.index 152 | expbuf.plane = 0 153 | expbuf.flags = os.O_CLOEXEC | os.O_RDWR 154 | 155 | ioctl(self.fd, v4l2.VIDIOC_EXPBUF, expbuf) 156 | buf.fd = expbuf.fd 157 | 158 | self.cap_bufs.append(buf) 159 | 160 | def connect_buffers(self, prev): 161 | for i in range(self.num_input_bufs): 162 | buf = self.input_bufs[i] 163 | bufprev = prev.cap_bufs[i] 164 | if buf.memory == v4l2.V4L2_MEMORY_DMABUF and bufprev.memory == v4l2.V4L2_MEMORY_MMAP: 165 | buf.m.planes[0].m.fd = bufprev.fd 166 | 167 | if not hasattr(self.pipe, 'input_bufs'): 168 | return 169 | 170 | for i in range(self.num_cap_bufs): 171 | buf = self.cap_bufs[i] 172 | bufp = self.pipe.input_bufs[i] 173 | if buf.memory == v4l2.V4L2_MEMORY_DMABUF and bufp.memory == v4l2.V4L2_MEMORY_MMAP: 174 | buf.m.planes[0].m.fd = bufp.fd 175 | 176 | self.pipe.connect_buffers(self) 177 | 178 | def request_key_frame(self): 179 | try: 180 | ctrl = v4l2.v4l2_control(v4l2.V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, 0) 181 | ioctl(self.fd, v4l2.VIDIOC_S_CTRL, ctrl) 182 | except Exception as e: 183 | logging.warning(f'{self.device}: request_key_frame: failed: {e}') 184 | 185 | def print_ctrls(self): 186 | self.ctrls.print_ctrls() 187 | 188 | def capture_loop(self): 189 | for buf in self.cap_bufs: 190 | ioctl(self.fd, v4l2.VIDIOC_QBUF, buf) 191 | 192 | planes = v4l2.v4l2_planes() 193 | qbuf = v4l2.v4l2_buffer() 194 | qbuf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 195 | qbuf.memory = self.cap_bufs[0].memory 196 | qbuf.length = 1 197 | qbuf.m.planes = planes 198 | 199 | while not self.stopped: 200 | try: 201 | ioctl(self.fd, v4l2.VIDIOC_DQBUF, qbuf) 202 | 203 | buf = self.cap_bufs[qbuf.index] 204 | buf.m.planes[0].bytesused = qbuf.m.planes[0].bytesused 205 | buf.timestamp = qbuf.timestamp 206 | 207 | # store bytesused the same place as without MPLANE 208 | buf.bytesused = qbuf.m.planes[0].bytesused 209 | 210 | self.pipe.write_buf(buf) 211 | ioctl(self.fd, v4l2.VIDIOC_QBUF, buf) 212 | except Exception as e: 213 | if not self.stopped: 214 | logging.warning(f'{self.device}: capture_loop: failed: {e}') 215 | 216 | 217 | def write_buf(self, ibuf): 218 | buf = self.input_bufs[ibuf.index] 219 | buf.timestamp = ibuf.timestamp 220 | buf.m.planes[0].bytesused = ibuf.bytesused 221 | 222 | if buf.memory == v4l2.V4L2_MEMORY_MMAP and ibuf.memory == v4l2.V4L2_MEMORY_MMAP: 223 | buf.buffer[:ibuf.bytesused] = ibuf.buffer[:ibuf.bytesused] 224 | 225 | # RPI decoder failed to decode with MJPGs with APP0 JFIF so patch to APP4 226 | if self.input_format == "MJPG": 227 | mbuf = buf.buffer if buf.memory == v4l2.V4L2_MEMORY_MMAP else ibuf.buffer 228 | app0_start = mbuf.find(JPEG_APP0, 0, 4) 229 | if app0_start != -1: 230 | mbuf[app0_start+1:app0_start+2] = JPEG_APP4_END 231 | app0_start = mbuf.find(JPEG_APP0, 4, 500) 232 | if app0_start != -1: 233 | mbuf[app0_start+1:app0_start+2] = JPEG_APP4_END 234 | 235 | #print(f"{self.device} writing input buf bytesused {buf.m.planes[0].bytesused} length {buf.m.planes[0].length} fd {buf.m.planes[0].m.fd} buf length {buf.length}") 236 | try: 237 | ioctl(self.fd, v4l2.VIDIOC_QBUF, buf) 238 | ioctl(self.fd, v4l2.VIDIOC_DQBUF, buf) 239 | except Exception as e: 240 | logging.warning(f'{self.device}: write_buf: failed: {e}') 241 | 242 | 243 | 244 | def start_capturing(self): 245 | ioctl(self.fd, v4l2.VIDIOC_STREAMON, struct.pack('I', v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) 246 | ioctl(self.fd, v4l2.VIDIOC_STREAMON, struct.pack('I', v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) 247 | self.capture_loop() 248 | 249 | 250 | def stop_capturing(self): 251 | self.stopped = True 252 | ioctl(self.fd, v4l2.VIDIOC_STREAMOFF, struct.pack('I', v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) 253 | ioctl(self.fd, v4l2.VIDIOC_STREAMOFF, struct.pack('I', v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) 254 | 255 | # Thread run 256 | def run(self): 257 | self.start_capturing() 258 | 259 | # Thread stop 260 | def stop(self): 261 | self.stop_capturing() 262 | self.join() 263 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fmp4streamer 2 | [How does it work](#how-does-it-work) | 3 | [Capabilities](#capabilities) | 4 | [Installation](#installation) | 5 | [Running](#running) | 6 | [Viewing](#viewing) | 7 | [Configuration](#configuration) | 8 | [Raspberry](#raspberry) | 9 | [Latency](#latency) | 10 | [Tested cameras](#tested-cameras) | 11 | [Roadmap](#roadmap) | 12 | [Motivation](#motivation) | 13 | [Changelog](https://github.com/soyersoyer/fmp4streamer/blob/main/CHANGELOG.md) 14 | 15 | 16 | Fmp4streamer streams your V4L2 camera directly to any browser and media player as H264 inside fragmented mp4. 17 | It is compatible with desktops and mobiles. You can add it your phone's home screen too! 18 |


19 | 20 | # How does it work 21 | Fmp4streamer setups the V4L2 device, reads the H264 or MJPGH264 stream from it (or the YUYV, MJPG stream and converts to H264 with a M2M V4L2 device), adds MP4 header (fragmented mp4 - fmp4) and serves it via HTTP. On the browser side it works with only one html5 video tag, no js needed. It's pretty lightweight. 22 | 23 | # Capabilities 24 | - Stream to multiple clients simultaneously (usually only limited by your network connection) 25 | - Support any resolution and framerate the video device can capture 26 | - Works in any Raspberry Pi with a camera module (If you are using the Raspberry OS Bullseye version please read the [Raspberry](#raspberry) section) 27 | - Able to handle high framerate (60-90 fps) streams 28 | - Able to handle cameras which only provide H264 inside MJPG format (UVC 1.5 H264 cameras, like Logitech C930e) 29 | - Able to convert MJPG camera stream to H264 via M2M decoder and encoder devices. 30 | - Able to put the camera into sleep mode when no one is watching the stream. 31 | - Able to stream to iPhone and Safari via HLS. 32 | - Add to home screen support for iPhone, Android. 33 | - Low cpu utilization 34 | 35 | # Installation 36 | ``` 37 | curl -sSL https://github.com/soyersoyer/fmp4streamer/archive/refs/tags/v3.4.7.tar.gz | tar -xvz 38 | ln -fns fmp4streamer-3.4.7 fmp4streamer 39 | ``` 40 | 41 | # Running 42 | - from the terminal 43 | ``` 44 | cd fmp4streamer 45 | python3 fmp4streamer.py 46 | ``` 47 | - enable running at startup and start now: 48 | ``` 49 | loginctl enable-linger 50 | systemctl --user enable --now ~/fmp4streamer/fmp4streamer.service 51 | ``` 52 | - disable running at startup and stop now: 53 | ``` 54 | systemctl --user disable --now fmp4streamer 55 | ``` 56 | - to stop the service 57 | ``` 58 | systemctl --user stop fmp4streamer 59 | ``` 60 | - to start the service 61 | ``` 62 | systemctl --user start fmp4streamer 63 | ``` 64 | - check the status of the service 65 | ``` 66 | systemctl --user status fmp4streamer 67 | ``` 68 | - watch the logs 69 | ``` 70 | journalctl --user-unit fmp4streamer 71 | ``` 72 | 73 | # Viewing 74 | When fmp4streamer.py is running the stream can be viewed from any browser via the following url. **_ip_address_** is the ip address or hostname of your computer, and **_port_** (default: 8000) is the port you set in the configuration section. 75 | ``` 76 | http://:/ 77 | ``` 78 | 79 | If you want to view the stream via your favourite media player, you can use the 80 | ``` 81 | http://:/stream.mp4 82 | ``` 83 | url. 84 | 85 | # Configuration 86 | 87 | You can start with the fmp4streamer.conf.dist: 88 | 89 | ``` 90 | cp fmp4streamer.conf.dist fmp4streamer.conf 91 | ``` 92 | 93 | Which contains: 94 | ```ini 95 | [server] 96 | listen = 97 | port = 8000 98 | [/dev/video0] 99 | width = 640 100 | height = 480 101 | fps = 30 102 | 103 | # Device capture format (default: H264) 104 | # H264, MJPGH264, YUYV, MJPG, JPEG 105 | # capture_format = H264 106 | 107 | # Decoder M2M device (default: disabled) 108 | # To decode the stream to a compatible format with the encoder (eg MJPG -> NV12 -> H264) 109 | # decoder = /dev/video10 110 | 111 | # Encoder M2M device (default: disabled) 112 | # To encode the stream to H264 (eg YUYV -> H264 or MJPG -> NV12 -> H264) 113 | # encoder = /dev/video11 114 | 115 | # Auto Sleep mode (default: yes) 116 | # Sleep the camera when no one is watching the stream 117 | # auto_sleep = yes 118 | 119 | # Sets the MP4 TRAK rotation matrix (default: 0) 120 | # 0, 90, 180, 270 121 | # rotation = 0 122 | 123 | # Controls 124 | 125 | # you can set any V4L2 control too, list them with the -l option 126 | h264_profile = High 127 | h264_level = 4.2 128 | h264_i_frame_period = 15 129 | 130 | uvcx_h264_i_frame_period = 1000 131 | uvcx_h264_profile = High 132 | 133 | # Advanced (change only if you know, what you are doing) 134 | 135 | # Buffer memory configurations (MMAP or DMABUF) 136 | # Sometimes contigous memory is not large enough for hardwares and tuning needed 137 | 138 | # default: DMABUF if encoder or decoder else MMAP 139 | # capture_memory = DMABUF 140 | 141 | # decoder_memory = MMAP-DMABUF 142 | # encoder_memory = MMAP-MMAP 143 | 144 | # Input format for the decoder (default: MJPG if capture_format == JPEG else capture_format) 145 | # If the capture_format isn't supported by the decoder directly, 146 | # but it can decode it with another format eg (capture_format = JPEG, decoder_input_format = MJPG) 147 | # decoder_input_format = MJPG 148 | 149 | # Input format for the encoder (default: NV12 if decoder else capture_format) 150 | # encoder_input_format = NV12 151 | ``` 152 | 153 | You can set all the V4L2 or UVCX H264 controls via the configuration file. List them with -l option: 154 | ``` 155 | $ python3 fmp4streamer.py -l 156 | Device: /dev/video0 157 | Name: Logitech Webcam C930e 158 | Driver: uvcvideo 159 | 160 | Controls 161 | brightness = 128 ( default: 128 min: 0 max: 255) 162 | contrast = 128 ( default: 128 min: 0 max: 255) 163 | saturation = 128 ( default: 128 min: 0 max: 255) 164 | white_balance_temperature_auto = 1 ( default: 1 min: 0 max: 1) 165 | gain = 255 ( default: 0 min: 0 max: 255) 166 | power_line_frequency = 50 Hz ( default: 60 Hz values: 'Disabled' '50 Hz' '60 Hz' ) 167 | white_balance_temperature = 4000 ( default: 4000 min: 2000 max: 7500) 168 | sharpness = 128 ( default: 128 min: 0 max: 255) 169 | backlight_compensation = 0 ( default: 0 min: 0 max: 1) 170 | exposure_auto = Aperture Priority Mode ( default: Aperture Priority Mode values: 'Manual Mode' 'Aperture Priority Mode' ) 171 | exposure_absolute = 250 ( default: 250 min: 3 max: 2047) 172 | exposure_auto_priority = 1 ( default: 0 min: 0 max: 1) 173 | pan_absolute = 0 ( default: 0 min: -36000 max: 36000 step: 3600) 174 | tilt_absolute = 0 ( default: 0 min: -36000 max: 36000 step: 3600) 175 | focus_absolute = 0 ( default: 0 min: 0 max: 255 step: 5) 176 | focus_auto = 1 ( default: 1 min: 0 max: 1) 177 | zoom_absolute = 100 ( default: 100 min: 100 max: 400) 178 | uvcx_logitech_led1_mode = Auto ( default: Off values: 'Off' 'On' 'Blink' 'Auto' ) 179 | uvcx_logitech_led1_frequency = 0 ( default: 0 min: 0 max: 255) 180 | uvcx_h264_stream_mux = H264 ( default: None values: 'None' 'H264' ) 181 | uvcx_h264_width = 1280 ( default: 1920 min: 160 max: 1920) 182 | uvcx_h264_height = 720 ( default: 1080 min: 120 max: 1080) 183 | uvcx_h264_frame_interval = 333333 ( default: 333333 min: 333333 max: 2000000) 184 | uvcx_h264_bitrate = 3000000 ( default: 3000000 min: 64000 max: 12000000) 185 | uvcx_h264_rate_control_mode = VBR ( default: CBR values: 'CBR' 'VBR' 'Const QP' ) 186 | uvcx_h264_profile = High ( default: Constrained values: 'Constrained' 'Baseline' 'Main' 'High' ) 187 | uvcx_h264_i_frame_period = 1000 ( default: 10000 min: 0 max: 50000) 188 | uvcx_h264_slice_mode = Off ( default: SlicesPerFrame values: 'Off' 'BitsPerSlice' 'MBsPerSlice' 'SlicesPerFrame' ) 189 | uvcx_h264_slice_units = 4 ( default: 4 min: 0 max: 68) 190 | uvcx_h264_entropy = CAVLC ( default: CAVLC values: 'CAVLC' 'CABAC' ) 191 | uvcx_h264_usage = Realtime ( default: Realtime values: 'Realtime' 'Broadcast' 'Storage' ) 192 | uvcx_h264_leaky_bucket_size = 1000 ( default: 1000 min: 500 max: 4000) 193 | 194 | To set one, put ctrl_name = Value into fmp4streamer.conf under the device 195 | ``` 196 | 197 | # Raspberry 198 | 199 | The Raspberry PI camera works with the default config on Raspberry OS Buster version. 200 | If you are using Bullseye with the PI cameras, you should setup the [old camera stack](https://forums.raspberrypi.com/viewtopic.php?t=323390) 201 | 202 | 203 | > Edit /boot/config.txt, remove the line "camera_auto_detect=1", and add "start_x=1" and "gpu_mem=128". 204 | > Rebooting at this stage will reload the old V4L2 driver. 205 | 206 | 207 | If you have a raspberry with an USB camera which supports YUYV format, you can use this configuration: 208 | 209 | ```ini 210 | [server] 211 | listen = 212 | port = 8000 213 | 214 | [/dev/video0] 215 | width = 640 216 | height = 480 217 | fps = 30 218 | capture_format = YUYV 219 | encoder = /dev/video11 220 | 221 | [/dev/video11] 222 | # you can set any V4L2 control too, list them with the -l option 223 | h264_profile = High 224 | h264_level = 4.2 225 | h264_i_frame_period = 15 226 | ``` 227 | 228 | If you have a raspberry with an USB camera which supports MJPG format, you can use this configuration: 229 | 230 | ```ini 231 | [server] 232 | listen = 233 | port = 8000 234 | 235 | [/dev/video0] 236 | width = 640 237 | height = 480 238 | fps = 30 239 | capture_format = MJPG 240 | decoder = /dev/video10 241 | encoder = /dev/video11 242 | 243 | [/dev/video11] 244 | # you can set any V4L2 control too, list them with the -l option 245 | h264_profile = High 246 | h264_level = 4.2 247 | h264_i_frame_period = 15 248 | ``` 249 | 250 | 251 | # Latency 252 | 253 | You can reduce the latency with lower I-Frame periods. You can set with the `h264_i_frame_period` or `uvcx_h264_i_frame_period` controls. 254 | 255 | # Tested Cameras 256 | 257 | - Raspberry PI Camera V1 258 | - Raspberry PI Camera V2 259 | - Logitech C270 (works with capture_format = YUYV or MJPG with the Raspiberry pi's H264 encoder) 260 | - Logitech C920 V1 (046d:082d) (works with capture_format = H264 or YUYV or MJPG) (Thanks @balazspeczeli for the camera) 261 | - Logitech C922 Pro (046d:085c) (before 2020) (works with capture_format = MJPGH264 or YUYV or MJPG) 262 | - Logitech C925e (046d:085b) (works with capture_format = MJPGH264 or YUYV or MJPG) 263 | - Logitech C930e (046d:0843) (works with capture_format = MJPGH264 or YUYV or MJPG) 264 | - Logitech BRIO (046d:085e) (works with capture_format = YUYV or MJPG) 265 | - Razer Kiyo Pro (1532:0e05) (works with capture_format = YUYV) (MJPG, H264 formats have various issues. According to the Razer support, they focus only on the Windows/OBSStudio, they don't care about USB standards or Linux.) 266 | 267 | # Roadmap 268 | - [x] Use V4L2 instead of raspivid 269 | - [x] Use V4L2 M2M H264 encoder if the camera doesn't have H264 capability 270 | - [x] Using UVC H264 extension to support cameras embedding H264 into MJPG capture format 271 | - [x] Use V4L2 M2M decoder device for MJPG streams 272 | - [ ] Adding AAC audio to the stream from ALSA 273 | - [ ] Support multiple cameras 274 | - [ ] Rewrite to Rust 275 | - [ ] Video and audio Recording 276 | - [ ] Motion detection based on H264 vectors 277 | - [ ] Configuration over Web UI 278 | 279 | # Motivation 280 | I wanted to stream my raspberry camera to the browser. I found some solution, but I think they are not optimal or too heavy (more than 100MB) or too hard to install, so I decided to write one which doesn't have too many dependencies. 281 | 282 | Fmp4streamer doesn't have any dependencies other than Python and V4L2, but they are installed by default. 283 | 284 | It adds fMP4 (fragmented MP4) headers to the h264 stream. It uses the python http server to serve it to the browser. 285 | 286 | The browsers play it with only one html5 video tag. No javascript needed. 287 | 288 | Safari on iOS plays it only with HLS playlists, but it works too. And the playlists added to the index.html of course. 289 | 290 | ## Inspired from 291 | 292 | [UV4L](https://www.linux-projects.org/uv4l/) 293 | 294 | [131/h264-live-player](https://github.com/131/h264-live-player) 295 | 296 | [dans98/pi-h264-to-browser](https://github.com/dans98/pi-h264-to-browser) 297 | 298 | [samirkumardas/jmuxer](https://github.com/samirkumardas/jmuxer) 299 | 300 | [Media Source Extensions](https://caniuse.com/mediasource) 301 | 302 | [thinkski/berrymse](https://github.com/thinkski/berrymse) 303 | -------------------------------------------------------------------------------- /fmp4streamer.py: -------------------------------------------------------------------------------- 1 | import io, socketserver, logging, configparser, getopt, sys, socket, os 2 | from http import server 3 | from time import time 4 | 5 | import bmff 6 | from v4l2camera import V4L2Camera, CameraSleeper 7 | from h264 import H264Parser, H264NALU 8 | 9 | def get_index_html(codec): 10 | return f''' 11 | 12 | 13 | 14 | 15 | fmp4streamer 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 36 | 37 | 38 | '''.encode('utf-8') 39 | 40 | def get_stream_m3u8(width, height, codec): 41 | return f'''#EXTM3U 42 | #EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION={width}x{height},CODECS="{codec}" 43 | streaminf.m3u8 44 | '''.encode('utf-8') 45 | 46 | def get_streaminf_m3u8(): 47 | return f'''#EXTM3U 48 | #EXT-X-TARGETDURATION:49057 49 | #EXT-X-VERSION:4 50 | #EXTINF:49057.00, 51 | stream.mp4?{int(time())} 52 | #EXT-X-ENDLIST 53 | '''.encode('utf-8') 54 | 55 | def get_manifest(): 56 | return '''{ 57 | "name": "Fmp4streamer", 58 | "short_name": "Fmp4streamer", 59 | "icons": [ 60 | { 61 | "src": "logo.png", 62 | "sizes": "128x128", 63 | "type": "image/png" 64 | }, 65 | ], 66 | "start_url": ".", 67 | "display": "standalone", 68 | "background_color": "#303030", 69 | "theme_color": "#303030" 70 | } 71 | '''.encode('utf-8') 72 | 73 | class MP4Writer: 74 | def __init__(self, w, width, height, rotation, timescale, sps, pps): 75 | if not sps or not pps: 76 | raise ValueError('MP4Writer: sps, pps NALUs are missing!') 77 | 78 | self.seq = 0 79 | self.sps = sps 80 | self.pps = pps 81 | self.start_secs = 0 82 | self.start_usecs = 0 83 | 84 | self.framebuf = io.BytesIO() 85 | 86 | self.w = w 87 | self.width = width 88 | self.height = height 89 | self.rotation = rotation 90 | self.timescale = timescale 91 | self.timescaleusec = timescale / 1000000 92 | self.decodetime = 0 93 | self.prevtime = 0 94 | 95 | self.write_header() 96 | 97 | 98 | def write_header(self): 99 | buf = io.BytesIO() 100 | bmff.write_ftyp(buf) 101 | bmff.write_moov(buf, self.width, self.height, self.rotation, self.timescale, self.sps, self.pps) 102 | self.w.write(buf.getbuffer()) 103 | 104 | def add_frame(self, nalus, frame_secs, frame_usecs): 105 | nalutype = H264NALU.get_type(nalus[0]) 106 | 107 | # our first frame should have SPS, PPS, IDR NALUS, so wait until we have an IDR or SPS+PPS+IDR 108 | if self.seq == 0 and (nalutype != H264NALU.IDRTYPE and nalutype != H264NALU.SPSTYPE): 109 | return 110 | 111 | # we have IDR or SPS+PPS+IDR 112 | if nalutype == H264NALU.IDRTYPE: 113 | self.write_frame([self.sps, self.pps] + nalus, frame_secs, frame_usecs, is_idr=True) 114 | elif nalutype == H264NALU.SPSTYPE: 115 | self.write_frame(nalus, frame_secs, frame_usecs, is_idr=True) 116 | elif nalutype == H264NALU.NONIDRTYPE: 117 | self.write_frame(nalus, frame_secs, frame_usecs, is_idr=False) 118 | 119 | def write_frame(self, nalus, frame_secs, frame_usecs, is_idr): 120 | if self.seq == 0: 121 | self.start_secs = frame_secs 122 | self.start_usecs = frame_usecs 123 | sampleduration = 1 124 | else: 125 | sampleduration = round( 126 | (frame_secs - self.start_secs) * self.timescale + 127 | (frame_usecs - self.start_usecs) * self.timescaleusec - 128 | self.decodetime) 129 | 130 | mdatsize = bmff.get_mdat_size(nalus) 131 | self.framebuf.seek(0) 132 | bmff.write_moof(self.framebuf, self.seq, mdatsize, is_idr, sampleduration, self.decodetime) 133 | bmff.write_mdat(self.framebuf, nalus) 134 | self.w.write(self.framebuf.getbuffer()[:bmff.MOOFSIZE + mdatsize]) 135 | 136 | self.seq += 1 137 | self.decodetime += sampleduration 138 | 139 | 140 | class StreamingHandler(server.BaseHTTPRequestHandler): 141 | def do_GET(self): 142 | if self.path == '/': 143 | self.send_response(301) 144 | self.send_header('Location', '/index.html') 145 | self.end_headers() 146 | elif self.path == '/index.html': 147 | self.send_response(200) 148 | self.send_header('Age', '0') 149 | self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') 150 | self.send_header('Content-Type', 'text/html') 151 | indexhtml = get_index_html(config.codec()) 152 | self.send_header('Content-Length', len(indexhtml)) 153 | self.end_headers() 154 | self.wfile.write(indexhtml) 155 | elif self.path == '/manifest.json': 156 | self.send_response(200) 157 | self.send_header('Age', '0') 158 | self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') 159 | self.send_header('Content-Type', 'application/json') 160 | data = get_manifest() 161 | self.send_header('Content-Length', len(data)) 162 | self.end_headers() 163 | self.wfile.write(data) 164 | elif self.path == '/logo.png' or self.path == '/apple-touch-icon.png': 165 | self.send_response(200) 166 | self.send_header('Content-Type', 'image/png') 167 | f = open('logo.png', 'rb') 168 | data = f.read() 169 | self.send_header('Content-Length', len(data)) 170 | self.end_headers() 171 | self.wfile.write(data) 172 | elif self.path == '/stream.m3u8': 173 | self.send_response(200) 174 | self.send_header('Age', '0') 175 | self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') 176 | self.send_header('Content-Type', 'application/x-mpegURL') 177 | streamm3u8 = get_stream_m3u8(config.width(), config.height(), config.codec()) 178 | self.send_header('Content-Length', len(streamm3u8)) 179 | self.end_headers() 180 | self.wfile.write(streamm3u8) 181 | elif self.path == '/streaminf.m3u8': 182 | self.send_response(200) 183 | self.send_header('Age', '0') 184 | self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') 185 | self.send_header('Content-Type', 'application/x-mpegURL') 186 | streaminfm3u8 = get_streaminf_m3u8() 187 | self.send_header('Content-Length', len(streaminfm3u8)) 188 | self.end_headers() 189 | self.wfile.write(streaminfm3u8) 190 | elif self.path.startswith('/stream.mp4'): 191 | self.send_response(200) 192 | self.send_header('Age', '0') 193 | self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') 194 | self.send_header('Content-Type', f'video/mp4; codecs="{config.codec()}"') 195 | self.end_headers() 196 | try: 197 | if not camera.sleeping: 198 | camera.request_key_frame() 199 | cameraSleeper.add_client() 200 | mp4_writer = MP4Writer(self.wfile, config.width(), config.height(), config.rotation(), config.timescale(), h264parser.sps, h264parser.pps) 201 | while True: 202 | nalus, frame_secs, frame_usecs = h264parser.read_frame() 203 | mp4_writer.add_frame(nalus, frame_secs, frame_usecs) 204 | except Exception as e: 205 | cameraSleeper.remove_client() 206 | self.log_message(f'Removed streaming client {self.client_address} {e}') 207 | else: 208 | self.send_error(404) 209 | self.end_headers() 210 | 211 | 212 | class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer): 213 | allow_reuse_address = True 214 | daemon_threads = True 215 | def server_bind(self): 216 | # disable Nagle's algorithm for lower latency 217 | self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 218 | server.HTTPServer.server_bind(self) 219 | 220 | def start(self): 221 | try: 222 | self.serve_forever() 223 | except KeyboardInterrupt: 224 | pass 225 | finally: 226 | self.server_close() 227 | 228 | 229 | class Config(configparser.ConfigParser): 230 | def __init__(self, configfile): 231 | super(Config, self).__init__({ 232 | 'width': 640, 233 | 'height': 480, 234 | 'fps': 30, 235 | }) 236 | self.read_dict({'server': {'listen': '', 'port': 8000, 'priority': 0}}) 237 | 238 | if len(self.read(configfile)) == 0: 239 | logging.warning(f'Couldn\'t read {configfile}, using default config') 240 | 241 | if len(self.sections()) == 1: 242 | self.add_section('/dev/video0') 243 | 244 | self.device = self.get_devices()[0] 245 | 246 | def get_devices(self): 247 | return [s for s in self.sections() if s != 'server'] 248 | 249 | def get_device(self): 250 | return self.device 251 | 252 | def width(self): 253 | return int(self[self.device]['width']) 254 | 255 | def height(self): 256 | return int(self[self.device]['height']) 257 | 258 | def fps(self): 259 | return int(self[self.device]['fps']) 260 | 261 | def rotation(self): 262 | return int(self[self.device].get('rotation', 0)) 263 | 264 | def sampleduration(self): 265 | return 500 266 | 267 | def timescale(self): 268 | return self.fps() * self.sampleduration() 269 | 270 | def h264_profile(self): 271 | return self[self.device].get('h264_profile', 'High') 272 | 273 | def h264_level(self): 274 | return self[self.device].get('h264_level', '4') 275 | 276 | def codec(self): 277 | profiles = {'High' : '6400', 'Main': '4d00', 'Baseline': '4200'} 278 | levels = {'4': '28', '4.1': '29', '4.2': '2a'} 279 | codec = 'avc1.' + profiles.get(self.h264_profile(), '6400') + levels.get(self.h264_level(), '28') 280 | return codec 281 | 282 | def usage(): 283 | print(f'usage: python3 {sys.argv[0]} [--help] [--list-controls] [--config CONFIG]\n') 284 | print(f'optional arguments:') 285 | print(f' -h, --help show this help message and exit') 286 | print(f' -l, --list-controls list the v4l2 controls and values for the camera') 287 | print(f' -c CONFIG, --config CONFIG use CONFIG as a config file, default: fmp4streamer.conf') 288 | 289 | 290 | try: 291 | arguments, values = getopt.getopt(sys.argv[1:], "hlc:", ["help", "list-controls","config="]) 292 | except getopt.error as err: 293 | print(err) 294 | usage() 295 | sys.exit(2) 296 | 297 | list_controls = False 298 | configfile = "fmp4streamer.conf" 299 | 300 | for current_argument, current_value in arguments: 301 | if current_argument in ('-h', '--help'): 302 | usage() 303 | sys.exit(0) 304 | elif current_argument in ("-l", "--list-controls"): 305 | list_controls = True 306 | elif current_argument in ("-c", "--config"): 307 | configfile = current_value 308 | 309 | config = Config(configfile) 310 | device = config.get_device() 311 | 312 | priority = config.getint('server', 'priority') 313 | try: 314 | os.setpriority(os.PRIO_PROCESS, 0, priority) 315 | except Exception as e: 316 | logging.warning(f'os.setpriority(os.PRIO_PROCESS, 0, {priority}) failed: {e}') 317 | 318 | h264parser = H264Parser() 319 | camera = V4L2Camera(device, h264parser, config) 320 | cameraSleeper = CameraSleeper(camera) 321 | 322 | if list_controls: 323 | camera.print_ctrls() 324 | print(f'To set one, put ctrl_name = Value into {configfile} under the device') 325 | sys.exit(0) 326 | 327 | camera.start() 328 | 329 | print(f'Waiting for the first h264 frame...', end="", flush=True) 330 | h264parser.read_frame() 331 | print(f' ok') 332 | 333 | camera.sleep() 334 | 335 | server = StreamingServer((config.get('server', 'listen'), config.getint('server', 'port')), StreamingHandler) 336 | print(f'Fmp4streamer will now start listening on {server.server_address}') 337 | server.start() 338 | camera.stop() 339 | -------------------------------------------------------------------------------- /v4l2camera.py: -------------------------------------------------------------------------------- 1 | from fcntl import ioctl 2 | from threading import Thread, Condition, Lock 3 | import mmap, os, struct, logging, sys 4 | 5 | import v4l2 6 | from v4l2ctrls import V4L2Ctrls 7 | from v4l2m2m import V4L2M2M 8 | from uvcxh264 import H264Ctrls 9 | from uvcxlogitech import LogitechCtrls 10 | 11 | class V4L2Camera(Thread): 12 | def __init__(self, device, pipe, config): 13 | super(V4L2Camera, self).__init__() 14 | self.condition = Condition() 15 | 16 | self.device = device 17 | self.stopped = False 18 | self.sleeping = False 19 | self.pipe = pipe 20 | self.encoder = None 21 | self.decoder = None 22 | self.num_cap_bufs = 6 23 | self.cap_bufs = [] 24 | 25 | self.fd = os.open(self.device, os.O_RDWR, 0) 26 | 27 | params = dict(config.items(device)) 28 | width = capture_width = int(params.get('width')) 29 | height = capture_height = int(params.get('height')) 30 | fps = int(params.get('fps')) 31 | capture_format = params.get('capture_format', 'H264') 32 | self.auto_sleep = config.getboolean(device, 'auto_sleep', fallback=True) 33 | 34 | if capture_format == 'MJPGH264': 35 | params['uvcx_h264_stream_mux'] = 'H264' 36 | params['uvcx_h264_width'] = width 37 | params['uvcx_h264_height'] = height 38 | # limit the MJPG stream resolution to spare with the USB bandwidth 39 | capture_width = 640 40 | capture_height = 480 41 | 42 | self.ctrls = V4L2Ctrls(self.device, self.fd) 43 | self.ctrls.setup_v4l2_ctrls(params) 44 | 45 | self.h264_ctrls = H264Ctrls(self.device, self.fd) 46 | self.h264_ctrls.setup_ctrls(params) 47 | 48 | self.logitech_ctrls = LogitechCtrls(self.device, self.fd) 49 | self.logitech_ctrls.setup_ctrls(params) 50 | 51 | # use the native capture format without the extension 52 | capture_format_real = capture_format[0:4] 53 | self.init_device(capture_width, capture_height, capture_format_real) 54 | self.init_fps(fps) 55 | 56 | decoder = params.get('decoder') 57 | decoder_input_format = params.get('decoder_input_format', "MJPG" if capture_format == "JPEG" else capture_format) 58 | decoder_memory = params.get('decoder_memory', 'MMAP-DMABUF') 59 | 60 | encoder = params.get('encoder') 61 | encoder_input_format = params.get('encoder_input_format', 'NV12' if decoder else capture_format) 62 | encoder_memory = params.get('encoder_memory', 'MMAP-MMAP') 63 | 64 | 65 | capture_memory = params.get('capture_memory', 'DMABUF' if encoder or decoder else 'MMAP') 66 | 67 | 68 | 69 | if encoder: 70 | encoderparams = dict(config.items(encoder) if encoder in config else {}) 71 | self.encoder = V4L2M2M(encoder, self.pipe, encoderparams, width, height, encoder_input_format, 'H264', encoder_memory) 72 | self.pipe = self.encoder 73 | 74 | if decoder: 75 | camera_sizeimage = self.get_sizeimage() 76 | decoderparams = dict(config.items(decoder) if decoder in config else {}) 77 | self.decoder = V4L2M2M(decoder, self.encoder, decoderparams, width, height, decoder_input_format, encoder_input_format, decoder_memory, camera_sizeimage) 78 | self.pipe = self.decoder 79 | 80 | if capture_format not in ['H264', 'MJPGH264'] and not self.encoder: 81 | logging.error(f'{self.device}: capture format is not H264 or MJPGH264, please add the V4L2 M2M encoder (or decoder) devices to the config') 82 | sys.exit(3) 83 | 84 | if capture_format == 'MJPGH264': 85 | if not self.h264_ctrls.supported(): 86 | logging.error(f'{self.device}: capture format is MJPGH264, but the H264 UVC extension is not supported by the device. Muxing the H264 into the MJPG is impossible.') 87 | sys.exit(3) 88 | if not self.h264_ctrls.h264_muxing_supported(): 89 | logging.error(f'{self.device}: capture format is MJPGH264, but muxing the H264 into the MJPG is not supported by the device.') 90 | sys.exit(3) 91 | 92 | self.init_buffers(capture_memory) 93 | self.connect_buffers() 94 | 95 | def check_buffers(elem): 96 | if not hasattr(elem.pipe, 'input_bufs'): 97 | if elem.cap_bufs[0].memory != v4l2.V4L2_MEMORY_MMAP: 98 | logging.error(f'{elem.device}: the capture memory should be MMAP on the last element in the chain') 99 | sys.exit(3) 100 | return 101 | if elem.cap_bufs[0].memory == v4l2.V4L2_MEMORY_DMABUF and elem.pipe.input_bufs[0].memory == v4l2.V4L2_MEMORY_DMABUF: 102 | logging.error(f'{elem.device}: capture memory is DMABUF and it couldn\'t be passed to another DMABUF buffer, use MMAP at least one side.') 103 | sys.exit(3) 104 | check_buffers(elem.pipe) 105 | check_buffers(self) 106 | 107 | 108 | 109 | def init_device(self, width, height, capture_format): 110 | cap = v4l2.v4l2_capability() 111 | fmt = v4l2.v4l2_format() 112 | 113 | ioctl(self.fd, v4l2.VIDIOC_QUERYCAP, cap) 114 | 115 | if not (cap.capabilities & v4l2.V4L2_CAP_VIDEO_CAPTURE): 116 | logging.error(f'{self.device} is not a video capture device') 117 | sys.exit(3) 118 | 119 | if not (cap.capabilities & v4l2.V4L2_CAP_STREAMING): 120 | logging.error(f'{self.device} does not support streaming i/o') 121 | sys.exit(3) 122 | 123 | pix_fmt = v4l2.get_fourcc(capture_format) 124 | 125 | fmt.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE 126 | fmt.fmt.pix.width = width 127 | fmt.fmt.pix.height = height 128 | fmt.fmt.pix.pixelformat = pix_fmt 129 | fmt.fmt.pix.field = v4l2.V4L2_FIELD_ANY 130 | ioctl(self.fd, v4l2.VIDIOC_S_FMT, fmt) 131 | 132 | if fmt.fmt.pix.pixelformat != pix_fmt: 133 | logging.error(f'{self.device} {capture_format} format not available') 134 | sys.exit(3) 135 | 136 | if fmt.fmt.pix.width != width or fmt.fmt.pix.height != height: 137 | logging.error(f'{self.device} {width}x{height} resolution not available') 138 | sys.exit(3) 139 | 140 | 141 | def init_fps(self, fps): 142 | parm = v4l2.v4l2_streamparm() 143 | parm.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE 144 | parm.parm.capture.timeperframe.numerator = 1000 145 | parm.parm.capture.timeperframe.denominator = fps * parm.parm.capture.timeperframe.numerator 146 | 147 | try: 148 | ioctl(self.fd, v4l2.VIDIOC_S_PARM, parm) 149 | except Exception as e: 150 | logging.warning(f'{self.device} Can\'t set fps to {fps}: {e}') 151 | 152 | tf = parm.parm.capture.timeperframe 153 | if tf.denominator == 0 or tf.numerator == 0: 154 | logging.error(f'VIDIOC_S_PARM: Invalid frame rate {fps}') 155 | sys.exit(3) 156 | if fps != (tf.denominator / tf.numerator): 157 | logging.warning(f'{self.device} Can\'t set fps to {fps} using {tf.denominator / tf.numerator}') 158 | 159 | def get_sizeimage(self): 160 | fmt = v4l2.v4l2_format() 161 | fmt.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE 162 | ioctl(self.fd, v4l2.VIDIOC_G_FMT, fmt) 163 | return fmt.fmt.pix.sizeimage 164 | 165 | def init_buffers(self, capture_memory): 166 | req = v4l2.v4l2_requestbuffers() 167 | 168 | req.count = self.num_cap_bufs 169 | req.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE 170 | req.memory = v4l2.get_mem_type(capture_memory) 171 | 172 | 173 | try: 174 | ioctl(self.fd, v4l2.VIDIOC_REQBUFS, req) 175 | except Exception: 176 | logging.error(f'video buffer request failed on {self.device}') 177 | sys.exit(3) 178 | 179 | if req.count != self.num_cap_bufs: 180 | logging.error(f'Insufficient buffer memory on {self.device}') 181 | sys.exit(3) 182 | 183 | for i in range(req.count): 184 | buf = v4l2.v4l2_buffer() 185 | buf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE 186 | buf.memory = req.memory 187 | buf.index = i 188 | 189 | ioctl(self.fd, v4l2.VIDIOC_QUERYBUF, buf) 190 | 191 | #print(buf.m.offset, buf.length) 192 | 193 | if req.memory == v4l2.V4L2_MEMORY_MMAP: 194 | buf.buffer = mmap.mmap(self.fd, buf.length, 195 | flags=mmap.MAP_SHARED | 0x08000, #MAP_POPULATE 196 | prot=mmap.PROT_READ | mmap.PROT_WRITE, 197 | offset=buf.m.offset) 198 | 199 | expbuf = v4l2.v4l2_exportbuffer() 200 | expbuf.type = buf.type 201 | expbuf.index = buf.index 202 | expbuf.flags = os.O_CLOEXEC | os.O_RDWR 203 | 204 | ioctl(self.fd, v4l2.VIDIOC_EXPBUF, expbuf) 205 | buf.fd = expbuf.fd 206 | 207 | 208 | self.cap_bufs.append(buf) 209 | 210 | 211 | def connect_buffers(self): 212 | if not hasattr(self.pipe, 'input_bufs'): 213 | return 214 | 215 | for i in range(self.num_cap_bufs): 216 | buf = self.cap_bufs[i] 217 | bufp = self.pipe.input_bufs[i] 218 | if buf.memory == v4l2.V4L2_MEMORY_DMABUF and bufp.memory == v4l2.V4L2_MEMORY_MMAP: 219 | buf.m.fd = bufp.fd 220 | self.pipe.connect_buffers(self) 221 | 222 | def request_key_frame(self): 223 | if self.encoder: 224 | self.encoder.request_key_frame() 225 | elif self.h264_ctrls.supported(): 226 | self.h264_ctrls.request_h264_idr() 227 | else: 228 | self.ctrls.request_key_frame() 229 | 230 | def print_ctrls(self): 231 | self.ctrls.print_ctrls() 232 | self.logitech_ctrls.print_ctrls() 233 | self.h264_ctrls.print_ctrls() 234 | print() 235 | if self.decoder: 236 | self.decoder.print_ctrls() 237 | print() 238 | if self.encoder: 239 | self.encoder.print_ctrls() 240 | print() 241 | 242 | def capture_loop(self): 243 | for buf in self.cap_bufs: 244 | ioctl(self.fd, v4l2.VIDIOC_QBUF, buf) 245 | 246 | qbuf = v4l2.v4l2_buffer() 247 | qbuf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE 248 | qbuf.memory = self.cap_bufs[0].memory 249 | 250 | while not self.stopped and not self.sleeping: 251 | ioctl(self.fd, v4l2.VIDIOC_DQBUF, qbuf) 252 | 253 | buf = self.cap_bufs[qbuf.index] 254 | buf.bytesused = qbuf.bytesused 255 | buf.timestamp = qbuf.timestamp 256 | 257 | self.pipe.write_buf(buf) 258 | 259 | ioctl(self.fd, v4l2.VIDIOC_QBUF, buf) 260 | 261 | 262 | 263 | def start_capturing(self): 264 | while not self.stopped: 265 | # we have to setup the h264 ctrls before every streamon 266 | self.h264_ctrls.refresh_ctrls() 267 | ioctl(self.fd, v4l2.VIDIOC_STREAMON, struct.pack('I', v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE)) 268 | self.capture_loop() 269 | ioctl(self.fd, v4l2.VIDIOC_STREAMOFF, struct.pack('I', v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE)) 270 | with self.condition: 271 | while self.sleeping and not self.stopped: 272 | self.condition.wait() 273 | 274 | def stop_capturing(self): 275 | with self.condition: 276 | self.stopped = True 277 | self.condition.notify_all() 278 | 279 | 280 | def sleep(self): 281 | if not self.auto_sleep: 282 | return 283 | self.sleeping = True 284 | 285 | def wakeup(self): 286 | if not self.auto_sleep: 287 | return 288 | with self.condition: 289 | self.sleeping = False 290 | self.condition.notify_all() 291 | 292 | # Thread run 293 | def run(self): 294 | if self.encoder: 295 | self.encoder.start() 296 | if self.decoder: 297 | self.decoder.start() 298 | self.start_capturing() 299 | 300 | # Thread stop 301 | def stop(self): 302 | self.stop_capturing() 303 | self.join() 304 | if self.decoder: 305 | self.decoder.stop() 306 | if self.encoder: 307 | self.encoder.stop() 308 | 309 | class CameraSleeper: 310 | def __init__(self, camera): 311 | self.camera = camera 312 | self.clients = 0 313 | self.clients_lock = Lock() 314 | 315 | def add_client(self): 316 | if not self.camera.auto_sleep: 317 | return 318 | with self.clients_lock: 319 | if self.clients == 0: 320 | self.camera.wakeup() 321 | self.clients += 1 322 | 323 | def remove_client(self): 324 | if not self.camera.auto_sleep: 325 | return 326 | with self.clients_lock: 327 | self.clients -= 1 328 | if self.clients == 0: 329 | self.camera.sleep() 330 | -------------------------------------------------------------------------------- /uvcxh264.py: -------------------------------------------------------------------------------- 1 | import uvcx, logging, ctypes 2 | 3 | # H264 extension GUID a29e7641-de04-47e3-8b2b-f4341aff003b 4 | UVC_H264_GUID = b'\x41\x76\x9e\xa2\x04\xde\xe3\x47\x8b\x2b\xf4\x34\x1a\xff\x00\x3b' 5 | 6 | VIDEO_CONFIG_PROBE = 0x01 7 | VIDEO_CONFIG_COMMIT = 0x02 8 | RATE_CONTROL_MODE = 0x03 9 | TEMPORAL_SCALE_MODE = 0x04 10 | SPATIAL_SCALE_MODE = 0x05 11 | SNR_SCALE_MODE = 0x06 12 | LTR_BUFFER_SIZE_CONTROL = 0x07 13 | LTR_PICTURE_CONTROL = 0x08 14 | PICTURE_TYPE_CONTROL = 0x09 15 | VERSION = 0x0A 16 | ENCODER_RESET = 0x0B 17 | FRAMERATE_CONFIG = 0x0C 18 | VIDEO_ADVANCE_CONFIG = 0x0D 19 | BITRATE_LAYERS = 0x0E 20 | QP_STEPS_LAYERS = 0x0F 21 | 22 | BMHINTS_NONE = 0x0000 23 | BMHINTS_RESOLUTION = 0x0001 24 | BMHINTS_PROFILE = 0x0002 25 | BMHINTS_RATECONTROL = 0x0004 26 | BMHINTS_USAGE = 0x0008 27 | BMHINTS_SLICEMODE = 0x0010 28 | BMHINTS_SLICEUNITS = 0x0020 29 | BMHINTS_MVCVIEW = 0x0040 30 | BMHINTS_TEMPORAL = 0x0080 31 | BMHINTS_SNR = 0x0100 32 | BMHINTS_SPATIAL = 0x0200 33 | BMHINTS_SPATIAL_RATIO = 0x0400 34 | BMHINTS_FRAME_INTERVAL = 0x0800 35 | BMHINTS_LEAKY_BKT_SIZE = 0x1000 36 | BMHINTS_BITRATE = 0x2000 37 | BMHINTS_ENTROPY = 0x4000 38 | BMHINTS_IFRAMEPERIOD = 0x8000 39 | 40 | PROFILE_CONSTRAINED_BASELINE = 0x4240 41 | PROFILE_BASELINE = 0x4200 42 | PROFILE_MAIN = 0x4D00 43 | PROFILE_HIGH = 0x6400 44 | 45 | RATECONTROL_CBR = 0x01 46 | RATECONTROL_VBR = 0x02 47 | RATECONTROL_CONST_QP = 0x03 48 | 49 | QP_STEPS_I_FRAME_TYPE = 0x01 50 | QP_STEPS_P_FRAME_TYPE = 0x02 51 | QP_STEPS_B_FRAME_TYPE = 0x04 52 | QP_STEPS_ALL_FRAME_TYPES = 0x07 53 | 54 | SLICE_MODE_OFF = 0x00 55 | SLICE_MODE_BITS_PER_SLICE = 0x01 56 | SLICE_MODE_MBS_PER_SLICE = 0x02 57 | SLICE_MODE_SLICES_PER_FRAME = 0x03 58 | 59 | ENTROPY_CAVLC = 0x00 60 | ENTROPY_CABAC = 0x01 61 | 62 | USAGE_REALTIME = 0x01 63 | USAGE_BROADCAST = 0x02 64 | USAGE_STORAGE = 0x03 65 | 66 | STREAM_MUX_DISABLED = 0x00 67 | STREAM_MUX_ENABLED = 0x01 68 | STREAM_MUX_H264 = 0x02 69 | STREAM_MUX_YUYV = 0x04 70 | STREAM_MUX_NV12 = 0x08 71 | STREAM_MUX_CONTAINER_MJPEG = 0x40 72 | 73 | STREAM_MUX_H264_ENABLED = STREAM_MUX_H264 | STREAM_MUX_ENABLED 74 | STREAM_MUX_YUYV_ENABLED = STREAM_MUX_YUYV | STREAM_MUX_ENABLED 75 | STREAM_MUX_NV12_ENABLED = STREAM_MUX_NV12 | STREAM_MUX_ENABLED 76 | 77 | PICTURE_TYPE_IFRAME = 0x0000 # Generate an IFRAME 78 | PICTURE_TYPE_IDR = 0x0001 # Generate an IDR 79 | PICTURE_TYPE_IDR_FULL = 0x0002 # Generate an IDR frame with new SPS and PPS 80 | 81 | class picture_type_control_t(ctypes.Structure): 82 | _fields_ = [ 83 | ('wLayerID', ctypes.c_uint16), 84 | ('wPicType', ctypes.c_uint16), 85 | ] 86 | 87 | class version_t(ctypes.Structure): 88 | _fields_ = [ 89 | ('wVersion', ctypes.c_uint16), 90 | ] 91 | 92 | 93 | class qp_steps_layers_t(ctypes.Structure): 94 | _fields_ = [ 95 | ('wLayerID', ctypes.c_uint16), 96 | ('bFrameType', ctypes.c_uint8), 97 | ('bMinQp', ctypes.c_uint8), 98 | ('bMaxQp', ctypes.c_uint8), 99 | ] 100 | 101 | class video_config_probe_commit(ctypes.Structure): 102 | _fields_ = [ 103 | ('dwFrameInterval', ctypes.c_uint32), 104 | ('dwBitRate', ctypes.c_uint32), 105 | ('bmHints', ctypes.c_uint16), 106 | ('wConfigurationIndex', ctypes.c_uint16), 107 | ('wWidth', ctypes.c_uint16), 108 | ('wHeight', ctypes.c_uint16), 109 | ('wSliceUnits', ctypes.c_uint16), 110 | ('wSliceMode', ctypes.c_uint16), 111 | ('wProfile', ctypes.c_uint16), 112 | ('wIFramePeriod', ctypes.c_uint16), 113 | ('wEstimatedVideoDelay', ctypes.c_uint16), 114 | ('wEstimatedMaxConfigDelay', ctypes.c_uint16), 115 | ('bUsageType', ctypes.c_uint8), 116 | ('bRateControlMode', ctypes.c_uint8), 117 | ('bTemporalScaleMode', ctypes.c_uint8), 118 | ('bSpatialScaleMode', ctypes.c_uint8), 119 | ('bSNRScaleMode', ctypes.c_uint8), 120 | ('bStreamMuxOption', ctypes.c_uint8), 121 | ('bStreamFormat', ctypes.c_uint8), 122 | ('bEntropyCABAC', ctypes.c_uint8), 123 | ('bTimestamp', ctypes.c_uint8), 124 | ('bNumOfReorderFrames', ctypes.c_uint8), 125 | ('bPreviewFlipped', ctypes.c_uint8), 126 | ('bView', ctypes.c_uint8), 127 | ('bReserved1', ctypes.c_uint8), 128 | ('bReserved2', ctypes.c_uint8), 129 | ('bStreamID', ctypes.c_uint8), 130 | ('bSpatialLayerRatio', ctypes.c_uint8), 131 | ('wLeakyBucketSize', ctypes.c_uint16), 132 | ] 133 | 134 | def request_h264_frame_type(fd, unit_id, type): 135 | picture_type_req = picture_type_control_t() 136 | picture_type_req.wLayerID = 0 137 | picture_type_req.wPicType = type 138 | 139 | uvcx.query_xu_control(fd, unit_id, PICTURE_TYPE_CONTROL, uvcx.UVC_SET_CUR, picture_type_req) 140 | 141 | def request_h264_idr(fd, unit_id): 142 | request_h264_frame_type(fd, unit_id, PICTURE_TYPE_IDR) 143 | 144 | def get_h264_default_config(fd, unit_id): 145 | config = video_config_probe_commit() 146 | uvcx.query_xu_control(fd, unit_id, VIDEO_CONFIG_PROBE, uvcx.UVC_GET_DEF, config) 147 | return config 148 | 149 | def get_h264_minimum_config(fd, unit_id): 150 | config = video_config_probe_commit() 151 | uvcx.query_xu_control(fd, unit_id, VIDEO_CONFIG_PROBE, uvcx.UVC_GET_MIN, config) 152 | return config 153 | 154 | def get_h264_maximum_config(fd, unit_id): 155 | config = video_config_probe_commit() 156 | uvcx.query_xu_control(fd, unit_id, VIDEO_CONFIG_PROBE, uvcx.UVC_GET_MAX, config) 157 | return config 158 | 159 | def get_h264_current_config(fd, unit_id): 160 | config = video_config_probe_commit() 161 | uvcx.query_xu_control(fd, unit_id, VIDEO_CONFIG_PROBE, uvcx.UVC_GET_CUR, config) 162 | return config 163 | 164 | def get_h264_resolution_config(fd, unit_id): 165 | config = video_config_probe_commit() 166 | uvcx.query_xu_control(fd, unit_id, VIDEO_CONFIG_PROBE, uvcx.UVC_GET_RES, config) 167 | return config 168 | 169 | def set_h264_config(fd, unit_id, config): 170 | uvcx.query_xu_control(fd, unit_id, VIDEO_CONFIG_COMMIT, uvcx.UVC_SET_CUR, config) 171 | 172 | def get_h264_version(fd, unit_id): 173 | version = version_t() 174 | uvcx.query_xu_control(fd, unit_id, VERSION, uvcx.UVC_GET_CUR, version) 175 | return version.wVersion 176 | 177 | class H264Ctrls: 178 | def __init__(self, device, fd): 179 | self.device = device 180 | self.fd = fd 181 | self.unit_id = uvcx.find_unit_id_in_sysfs(device, UVC_H264_GUID) 182 | self.get_device_controls() 183 | 184 | def supported(self): 185 | return self.unit_id != 0 186 | 187 | def h264_muxing_supported(self): 188 | return self.supported() and (self.maximum_config.bStreamMuxOption & STREAM_MUX_H264) > 0 189 | 190 | def get_device_controls(self): 191 | if not self.supported(): 192 | self.ctrls = [] 193 | return 194 | 195 | self.default_config = get_h264_default_config(self.fd, self.unit_id) 196 | self.minimum_config = get_h264_minimum_config(self.fd, self.unit_id) 197 | self.maximum_config = get_h264_maximum_config(self.fd, self.unit_id) 198 | self.current_config = get_h264_current_config(self.fd, self.unit_id) 199 | self.ctrls = [ 200 | Ctrl( 201 | 'uvcx_h264_stream_mux', 202 | BMHINTS_NONE, 203 | 'bStreamMuxOption', 204 | { 205 | STREAM_MUX_DISABLED: 'None', 206 | STREAM_MUX_H264_ENABLED: 'H264', 207 | } 208 | ), 209 | Ctrl( 210 | 'uvcx_h264_width', 211 | BMHINTS_RESOLUTION, 212 | 'wWidth', 213 | ), 214 | Ctrl( 215 | 'uvcx_h264_height', 216 | BMHINTS_RESOLUTION, 217 | 'wHeight', 218 | ), 219 | Ctrl( 220 | 'uvcx_h264_frame_interval', 221 | BMHINTS_FRAME_INTERVAL, 222 | 'dwFrameInterval', 223 | ), 224 | Ctrl( 225 | 'uvcx_h264_bitrate', 226 | BMHINTS_BITRATE, 227 | 'dwBitRate', 228 | ), 229 | Ctrl( 230 | 'uvcx_h264_rate_control_mode', 231 | BMHINTS_RATECONTROL, 232 | 'bRateControlMode', 233 | { 234 | RATECONTROL_CBR: 'CBR', 235 | RATECONTROL_VBR: 'VBR', 236 | RATECONTROL_CONST_QP: 'Const QP', 237 | } 238 | ), 239 | Ctrl( 240 | 'uvcx_h264_profile', 241 | BMHINTS_PROFILE, 242 | 'wProfile', 243 | { 244 | PROFILE_CONSTRAINED_BASELINE: 'Constrained', 245 | PROFILE_BASELINE: 'Baseline', 246 | PROFILE_MAIN: 'Main', 247 | PROFILE_HIGH: 'High', 248 | } 249 | ), 250 | Ctrl( 251 | 'uvcx_h264_i_frame_period', 252 | BMHINTS_IFRAMEPERIOD, 253 | 'wIFramePeriod', 254 | ), 255 | Ctrl( 256 | 'uvcx_h264_slice_mode', 257 | BMHINTS_SLICEMODE, 258 | 'wSliceMode', 259 | { 260 | SLICE_MODE_OFF: 'Off', 261 | SLICE_MODE_BITS_PER_SLICE: 'BitsPerSlice', 262 | SLICE_MODE_MBS_PER_SLICE: 'MBsPerSlice', 263 | SLICE_MODE_SLICES_PER_FRAME: 'SlicesPerFrame', 264 | } 265 | ), 266 | Ctrl( 267 | 'uvcx_h264_slice_units', 268 | BMHINTS_SLICEUNITS, 269 | 'wSliceUnits', 270 | ), 271 | Ctrl( 272 | 'uvcx_h264_entropy', 273 | BMHINTS_ENTROPY, 274 | 'bEntropyCABAC', 275 | { 276 | ENTROPY_CAVLC: 'CAVLC', 277 | ENTROPY_CABAC: 'CABAC', 278 | } 279 | ), 280 | Ctrl( 281 | 'uvcx_h264_usage', 282 | BMHINTS_USAGE, 283 | 'bUsageType', 284 | { 285 | USAGE_REALTIME: 'Realtime', 286 | USAGE_BROADCAST: 'Broadcast', 287 | USAGE_STORAGE: 'Storage', 288 | } 289 | ), 290 | Ctrl( 291 | 'uvcx_h264_leaky_bucket_size', 292 | BMHINTS_LEAKY_BKT_SIZE, 293 | 'wLeakyBucketSize', 294 | ), 295 | ] 296 | 297 | def print_ctrls(self): 298 | for c in self.ctrls: 299 | minimum = getattr(self.minimum_config, c.sname) 300 | maximum = getattr(self.maximum_config, c.sname) 301 | default = getattr(self.default_config, c.sname) 302 | value = getattr(self.current_config, c.sname) 303 | print(c.name, end = ' = ') 304 | if not c.menu: 305 | print('%a\t(' % value, 'default:', default, 'min:', minimum, 'max:', maximum, end = ')\n') 306 | else: 307 | valmenu = c.menu.get(value) 308 | defmenu = c.menu.get(default) 309 | print(f'{valmenu if valmenu else value}\t(', end = ' ') 310 | if defmenu: 311 | print(f'default: {defmenu}', end = ' ') 312 | print('values:', end = ' ') 313 | for k, v in c.menu.items(): 314 | print('%a' % v, end = ' ') 315 | print(')') 316 | 317 | def setup_ctrls(self, params): 318 | if not self.supported(): 319 | return 320 | 321 | for k, v in params.items(): 322 | if not k.startswith('uvcx_h264_'): 323 | continue 324 | ctrl = find_by_name(self.ctrls, k) 325 | if ctrl == None: 326 | logging.warning(f'H264Ctrls: can\'t find {k} control in {[c.name for c in self.ctrls]}') 327 | continue 328 | if ctrl.menu: 329 | menukey = find_by_value(ctrl.menu, v) 330 | if menukey == None: 331 | logging.warning(f'H264Ctrls: can\'t find {v} in {list(ctrl.menu.values())}') 332 | continue 333 | setattr(self.current_config, ctrl.sname, menukey) 334 | else: 335 | setattr(self.current_config, ctrl.sname, int(v)) 336 | self.current_config.bmHints |= ctrl.hint 337 | 338 | set_h264_config(self.fd, self.unit_id, self.current_config) 339 | current = get_h264_current_config(self.fd, self.unit_id) 340 | 341 | for k, v in params.items(): 342 | if not k.startswith('uvcx_h264_'): 343 | continue 344 | ctrl = find_by_name(self.ctrls, k) 345 | if ctrl == None: 346 | continue 347 | desired_value = getattr(self.current_config, ctrl.sname) 348 | current_value = getattr(current, ctrl.sname) 349 | if ctrl.menu: 350 | desired_value = ctrl.menu.get(desired_value, desired_value) 351 | current_value = ctrl.menu.get(current_value, current_value) 352 | if current_value != desired_value: 353 | logging.warning(f'H264Ctrls: failed to set {k} to {desired_value}, current value {current_value}\n') 354 | 355 | def refresh_ctrls(self): 356 | if not self.supported(): 357 | return 358 | set_h264_config(self.fd, self.unit_id, self.current_config) 359 | 360 | def request_h264_idr(self): 361 | request_h264_frame_type(self.fd, self.unit_id, PICTURE_TYPE_IDR) 362 | 363 | class Ctrl: 364 | def __init__(self, name, hint, sname, menu = None): 365 | self.name = name 366 | self.hint = hint 367 | self.sname = sname 368 | self.menu = menu 369 | 370 | def find_by_name(ctrls, name): 371 | for c in ctrls: 372 | if name == c.name: 373 | return c 374 | return None 375 | 376 | def find_by_value(menu, value): 377 | for k, v in menu.items(): 378 | if v == value: 379 | return k 380 | return None 381 | -------------------------------------------------------------------------------- /bmff.py: -------------------------------------------------------------------------------- 1 | from struct import pack 2 | 3 | HANDLERNAME = b'TinyStreamer' 4 | COMPATSTRING = b'isomiso2iso5avc1mp41' 5 | 6 | # functions unrolled to minimize function calls, because they are very slow in python 7 | 8 | # References: 9 | # ISO/IEC 14496 Part 12 10 | # ISO/IEC 14496 Part 15 11 | 12 | # ISO/IEC 14496 Part 14 is not used. 13 | 14 | COMPATSTRINGSIZE = 20 15 | FTYPSIZE = 8 + COMPATSTRINGSIZE + 8 16 | def write_ftyp(w): 17 | w.write(pack('>I 4s 4s I 20s', FTYPSIZE, b'ftyp', b'isom', 0x200, COMPATSTRING)) 18 | # w.write((FTYPSIZE).to_bytes(4, 'big')) 19 | # w.write(b'ftyp') 20 | # w.write(b'isom') # major brand 21 | # w.write((0x200).to_bytes(4, 'big')) # minor version 22 | # w.write(COMPATSTRING) # compatible brands 23 | 24 | MVHDSIZE = 100 + 8 25 | 26 | TKHDSIZE = 84 + 8 27 | 28 | MDHDSIZE = 24 + 8 29 | HDLRSIZE = 24 + len(HANDLERNAME) + 1 + 8 30 | 31 | VMHDSIZE = 12 + 8 32 | 33 | URLSIZE = 4 + 8 34 | DREFSIZE = 8 + URLSIZE + 8 35 | DINFSIZE = DREFSIZE + 8 36 | 37 | AVCCSIZEWOSPSPPS = 11 + 8 38 | AVC1SIZEWOSPSPPS = 78 + AVCCSIZEWOSPSPPS + 8 39 | STSDSIZEWOSPSPPS = 8 + AVC1SIZEWOSPSPPS + 8 40 | STSZSIZE = 12 + 8 41 | STSCSIZE = 8 + 8 42 | STTSSIZE = 8 + 8 43 | STCOSIZE = 8 + 8 44 | STBLSIZEWOSPSPPS = STSDSIZEWOSPSPPS + STSZSIZE + STSCSIZE + STTSSIZE + STCOSIZE + 8 45 | 46 | MINFSIZEWOSPSPPS = VMHDSIZE + DINFSIZE + STBLSIZEWOSPSPPS + 8 47 | 48 | MDIASIZEWOSPSPPS = MDHDSIZE + HDLRSIZE + MINFSIZEWOSPSPPS + 8 49 | 50 | TRAKSIZEWOSPSPPS = TKHDSIZE + MDIASIZEWOSPSPPS + 8 51 | 52 | MEHDSIZE = 8 + 8 53 | TREXSIZE = 24 + 8 54 | MVEXSIZE = MEHDSIZE + TREXSIZE + 8 55 | 56 | MOOVSIZEWOSPSPPS = MVHDSIZE + TRAKSIZEWOSPSPPS + MVEXSIZE + 8 57 | 58 | ROT0MATRIX = pack('>9I', 59 | 0x00010000, 0x00000000, 0x00000000, 60 | 0x00000000, 0x00010000, 0x00000000, 61 | 0x00000000, 0x00000000, 0x40000000 62 | ) 63 | 64 | ROT90MATRIX = pack('>9I', 65 | 0x00000000, 0x00010000, 0x00000000, 66 | 0xFFFF0000, 0x00000000, 0x00000000, 67 | 0x00000000, 0x00000000, 0x40000000 68 | ) 69 | 70 | ROT180MATRIX = pack('>9I', 71 | 0xFFFF0000, 0x00000000, 0x00000000, 72 | 0x00000000, 0xFFFF0000, 0x00000000, 73 | 0x00000000, 0x00000000, 0x40000000 74 | ) 75 | 76 | ROT270MATRIX = pack('>9I', 77 | 0x00000000, 0xFFFF0000, 0x00000000, 78 | 0x00010000, 0x00000000, 0x00000000, 79 | 0x00000000, 0x00000000, 0x40000000 80 | ) 81 | 82 | def write_moov(w, width, height, rotation, timescale, sps, pps): 83 | if rotation == 0: 84 | rot_matrix = ROT0MATRIX 85 | elif rotation == 90: 86 | rot_matrix = ROT90MATRIX 87 | elif rotation == 180: 88 | rot_matrix = ROT180MATRIX 89 | elif rotation == 270: 90 | rot_matrix = ROT270MATRIX 91 | else: 92 | raise ValueError(f'bmff: write_moov: rotation should be 0, 90, 180, 270 not {rotation}') 93 | 94 | w.write(pack('>I 4s', MOOVSIZEWOSPSPPS + len(sps) + len(pps), b'moov')) 95 | # w.write((MOOVSIZEWOSPSPPS + len(sps) + len(pps)).to_bytes(4, 'big')) 96 | # w.write(b'moov') 97 | 98 | w.write(pack('>I 4s I I I I I I H H II III III III IIIIIII', MVHDSIZE, b'mvhd', 99 | 0, 0, 0, timescale, 0, 0x00010000, 0x0100, 0, 0, 0, 100 | 0x00010000, 0, 0, 101 | 0, 0x00010000, 0, 102 | 0, 0, 0x40000000, 103 | 0, 0, 0, 0, 0, 0, 0)) 104 | # w.write(MVHDSIZE.to_bytes(4, 'big')) 105 | # w.write(b'mvhd') 106 | # w.write((0).to_bytes(4, 'big')) # version and flags 107 | # w.write((0).to_bytes(4, 'big')) # creation time 108 | # w.write((0).to_bytes(4, 'big')) # modification time 109 | # w.write((timescale).to_bytes(4, 'big')) # timescale 110 | # w.write((0).to_bytes(4, 'big')) # duration (all 1s == unknown) 111 | # w.write((0x00010000).to_bytes(4, 'big')) # rate (1.0 == normal) 112 | # w.write((0x0100).to_bytes(2, 'big')) # volume (1.0 == normal) 113 | # w.write((0).to_bytes(2, 'big')) # reserved 114 | # w.write((0).to_bytes(4, 'big')) # reserved 115 | # w.write((0).to_bytes(4, 'big')) # reserved 116 | # w.write((0x00010000).to_bytes(4, 'big')) # matrix 117 | # w.write((0x0).to_bytes(4, 'big')) # matrix 118 | # w.write((0x0).to_bytes(4, 'big')) # matrix 119 | # w.write((0x0).to_bytes(4, 'big')) # matrix 120 | # w.write((0x00010000).to_bytes(4, 'big')) # matrix 121 | # w.write((0x0).to_bytes(4, 'big')) # matrix 122 | # w.write((0x0).to_bytes(4, 'big')) # matrix 123 | # w.write((0x0).to_bytes(4, 'big')) # matrix 124 | # w.write((0x40000000).to_bytes(4, 'big')) # matrix 125 | # w.write((0).to_bytes(4, 'big')) # pre-defined 126 | # w.write((0).to_bytes(4, 'big')) # pre-defined 127 | # w.write((0).to_bytes(4, 'big')) # pre-defined 128 | # w.write((0).to_bytes(4, 'big')) # pre-defined 129 | # w.write((0).to_bytes(4, 'big')) # pre-defined 130 | # w.write((0).to_bytes(4, 'big')) # pre-defined 131 | # w.write((0).to_bytes(4, 'big')) # next track id 132 | 133 | w.write(pack('>I 4s', TRAKSIZEWOSPSPPS + len(sps) + len(pps), b'trak')) 134 | # w.write((TRAKSIZEWOSPSPPS + len(sps) + len(pps)).to_bytes(4, 'big')) 135 | # w.write(b'trak') 136 | 137 | w.write(pack('>I 4s I I I I I I I I H H H H 36s I I', TKHDSIZE, b'tkhd', 138 | 7, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, rot_matrix, 139 | int(width)<<16, int(height)<<16)) 140 | # w.write(TKHDSIZE.to_bytes(4, 'big')) 141 | # w.write(b'tkhd') 142 | # w.write((7).to_bytes(4, 'big')) # version and flags (track enabled) 143 | # w.write((0).to_bytes(4, 'big')) # creation time 144 | # w.write((0).to_bytes(4, 'big')) # modification time 145 | # w.write((1).to_bytes(4, 'big')) # track id 146 | # w.write((0).to_bytes(4, 'big')) # reserved 147 | # w.write((0).to_bytes(4, 'big')) # duration 148 | # w.write((0).to_bytes(4, 'big')) # reserved 149 | # w.write((0).to_bytes(4, 'big')) # reserved 150 | # w.write((0).to_bytes(2, 'big')) # layer 151 | # w.write((0).to_bytes(2, 'big')) # alternate group 152 | # w.write((0).to_bytes(2, 'big')) # volume (ignored for video tracks) 153 | # w.write((0).to_bytes(2, 'big')) # reserved 154 | # w.write((0x00010000).to_bytes(4, 'big')) # matrix 155 | # w.write((0x0).to_bytes(4, 'big')) # matrix 156 | # w.write((0x0).to_bytes(4, 'big')) # matrix 157 | # w.write((0x0).to_bytes(4, 'big')) # matrix 158 | # w.write((0x00010000).to_bytes(4, 'big')) # matrix 159 | # w.write((0x0).to_bytes(4, 'big')) # matrix 160 | # w.write((0x0).to_bytes(4, 'big')) # matrix 161 | # w.write((0x0).to_bytes(4, 'big')) # matrix 162 | # w.write((0x40000000).to_bytes(4, 'big')) # matrix 163 | # w.write((int(width)<<16).to_bytes(4, 'big')) # width (fixed-point 16.16 format) 164 | # w.write((int(height)<<16).to_bytes(4, 'big')) # height (fixed-point 16.16 format) 165 | 166 | w.write(pack('>I 4s', MDIASIZEWOSPSPPS + len(sps) + len(pps), b'mdia')) 167 | # w.write((MDIASIZEWOSPSPPS + len(sps) + len(pps)).to_bytes(4, 'big')) 168 | # w.write(b'mdia') 169 | 170 | w.write(pack('>I 4s I I I I I H H', MDHDSIZE, b'mdhd', 0, 0, 0, timescale, 0, 0x55c4, 0)) 171 | # w.write(MDHDSIZE.to_bytes(4, 'big')) 172 | # w.write(b'mdhd') 173 | # w.write((0).to_bytes(4, 'big')) # version and flags 174 | # w.write((0).to_bytes(4, 'big')) # creation time 175 | # w.write((0).to_bytes(4, 'big')) # modification time 176 | # w.write((timescale).to_bytes(4, 'big')) # timescale 177 | # w.write((0).to_bytes(4, 'big')) # duration 178 | # w.write((0x55c4).to_bytes(2, 'big')) # language ('und' == undefined) 179 | # w.write((0).to_bytes(2, 'big')) # pre-defined 180 | 181 | w.write(pack('>I 4s I I 4s I I I %ds B' % len(HANDLERNAME), HDLRSIZE, b'hdlr', 182 | 0, 0, b'vide', 0, 0, 0, HANDLERNAME, 0)) 183 | # w.write(HDLRSIZE.to_bytes(4, 'big')) 184 | # w.write(b'hdlr') 185 | # w.write((0).to_bytes(4, 'big')) # version and flags 186 | # w.write((0).to_bytes(4, 'big')) # pre-defined 187 | # w.write(b'vide') # handler type 188 | # w.write((0).to_bytes(4, 'big')) # reserved 189 | # w.write((0).to_bytes(4, 'big')) # reserved 190 | # w.write((0).to_bytes(4, 'big')) # reserved 191 | # w.write(HANDLERNAME) # name 192 | # w.write((0).to_bytes(1, 'big')) # null-terminator 193 | 194 | w.write(pack('>I 4s', MINFSIZEWOSPSPPS + len(sps) + len(pps), b'minf')) 195 | # w.write((MINFSIZEWOSPSPPS + len(sps) + len(pps)).to_bytes(4, 'big')) 196 | # w.write(b'minf') 197 | 198 | w.write(pack('>I 4s I H H H H', VMHDSIZE, b'vmhd', 1, 0, 0, 0, 0)) 199 | # w.write(VMHDSIZE.to_bytes(4, 'big')) 200 | # w.write(b'vmhd') 201 | # w.write((1).to_bytes(4, 'big')) # version and flags 202 | # w.write((0).to_bytes(2, 'big')) # graphics mode 203 | # w.write((0).to_bytes(2, 'big')) # opcolor 204 | # w.write((0).to_bytes(2, 'big')) # opcolor 205 | # w.write((0).to_bytes(2, 'big')) # opcolor 206 | 207 | w.write(pack('>I 4s', DINFSIZE, b'dinf')) 208 | # w.write((DINFSIZE).to_bytes(4, 'big')) 209 | # w.write(b'dinf') 210 | 211 | w.write(pack('>I 4s I I', DREFSIZE, b'dref', 0, 1)) 212 | # w.write((DREFSIZE).to_bytes(4, 'big')) 213 | # w.write(b'dref') 214 | # w.write((0).to_bytes(4, 'big')) # version and flags 215 | # w.write((1).to_bytes(4, 'big')) # entry count 216 | 217 | w.write(pack('>I 4s I', URLSIZE, b'url ', 1)) 218 | # w.write((URLSIZE).to_bytes(4, 'big')) 219 | # w.write(b'url ') 220 | # w.write((1).to_bytes(4, 'big')) # version and flags 221 | 222 | w.write(pack('>I 4s', STBLSIZEWOSPSPPS + len(sps) + len(pps), b'stbl')) 223 | # w.write((STBLSIZEWOSPSPPS + len(sps) + len(pps)).to_bytes(4, 'big')) 224 | # w.write(b'stbl') 225 | 226 | # Sample Table Box 227 | w.write(pack('>I 4s IH H', STSDSIZEWOSPSPPS + len(sps) + len(pps), b'stsd', 0, 0, 1)) 228 | # w.write((STSDSIZEWOSPSPPS + len(sps) + len(pps)).to_bytes(4, 'big')) 229 | # w.write(b'stsd') 230 | # w.write((0).to_bytes(6, 'big')) # reserved 231 | # w.write((1).to_bytes(2, 'big')) # deta reference index 232 | 233 | w.write(pack('>I 4s IH H H H I I I H H I I I H 32s H H', 234 | AVC1SIZEWOSPSPPS + len(sps) + len(pps), b'avc1', 235 | 0, 0, 1, 0, 0, 0, 0, 0, width, height, 0x00480000, 0x00480000, 0, 1, bytes(32), 0x18, 0xffff)) 236 | # w.write((AVC1SIZEWOSPSPPS + len(sps) + len(pps)).to_bytes(4, 'big')) 237 | # w.write(b'avc1') 238 | # w.write((0).to_bytes(6, 'big')) # reserved 239 | # w.write((1).to_bytes(2, 'big')) # data reference index 240 | # w.write((0).to_bytes(2, 'big')) # pre-defined 241 | # w.write((0).to_bytes(2, 'big')) # reserved 242 | # w.write((0).to_bytes(4, 'big')) # pre-defined 243 | # w.write((0).to_bytes(4, 'big')) # pre-defined 244 | # w.write((0).to_bytes(4, 'big')) # pre-defined 245 | # w.write((int(width)).to_bytes(2, 'big')) # width 246 | # w.write((int(height)).to_bytes(2, 'big')) # height 247 | # w.write((0x00480000).to_bytes(4, 'big')) # horizontal resolution: 72 dpi 248 | # w.write((0x00480000).to_bytes(4, 'big')) # vertical resolution: 72 dpi 249 | # w.write((0).to_bytes(4, 'big')) # data size: 0 250 | # w.write((1).to_bytes(2, 'big')) # frame count: 1 251 | # w.write(bytes(32)) # compressor name 252 | # w.write((0x18).to_bytes(2, 'big')) # depth 253 | # w.write((0xffff).to_bytes(2, 'big')) # pre-defined 254 | 255 | # MPEG-4 Part 15 extension 256 | # See ISO/IEC 14496-15:2004 5.3.4.1.2 257 | w.write(pack('>I 4s B B B B B B H %ds B H %ds' % (len(sps), len(pps)), 258 | AVCCSIZEWOSPSPPS + len(sps) + len(pps), b'avcC', 259 | 1, 0x64, 0x00, 0x2a, 0xff, 0xe1, 260 | len(sps), sps, 1, len(pps), pps)) 261 | # w.write((AVCCSIZEWOSPSPPS + len(sps) + len(pps)).to_bytes(4, 'big')) 262 | # w.write(b'avcC') 263 | # w.write((1).to_bytes(1, 'big')) # configuration version 264 | # w.write((0x64).to_bytes(1, 'big')) # H.264 profile (0x64 == high) 265 | # w.write((0x00).to_bytes(1, 'big')) # H.264 profile compatibility 266 | # w.write((0x2a).to_bytes(1, 'big')) # H.264 level (0x28 == 4.0, 0x2a == 4.2) 267 | # w.write((0xff).to_bytes(1, 'big')) # nal unit length - 1 (upper 6 bits == 1) 268 | # w.write((0xe1).to_bytes(1, 'big')) # number of sps (upper 3 bits == 1) 269 | # w.write((len(sps)).to_bytes(2, 'big')) 270 | # w.write(sps) 271 | # w.write((1).to_bytes(1, 'big')) # number of pps 272 | # w.write((len(pps)).to_bytes(2, 'big')) 273 | # w.write(pps) 274 | 275 | w.write(pack('>I 4s I I I', STSZSIZE, b'stsz', 0, 0, 0)) 276 | # w.write(STSZSIZE.to_bytes(4, 'big')) 277 | # w.write(b'stsz') 278 | # w.write((0).to_bytes(4, 'big')) # version and flags 279 | # w.write((0).to_bytes(4, 'big')) # sample size 280 | # w.write((0).to_bytes(4, 'big')) # sample count 281 | 282 | w.write(pack('>I 4s I I', STSCSIZE, b'stsc', 0, 0)) 283 | # w.write(STSCSIZE.to_bytes(4, 'big')) 284 | # w.write(b'stsc') 285 | # w.write((0).to_bytes(4, 'big')) # version and flags 286 | # w.write((0).to_bytes(4, 'big')) # entry count 287 | 288 | w.write(pack('>I 4s I I', STTSSIZE, b'stts', 0, 0)) 289 | # w.write(STTSSIZE.to_bytes(4, 'big')) 290 | # w.write(b'stts') 291 | # w.write((0).to_bytes(4, 'big')) # version and flags 292 | # w.write((0).to_bytes(4, 'big')) # entry count 293 | 294 | w.write(pack('>I 4s I I', STCOSIZE, b'stco', 0, 0)) 295 | # w.write(STCOSIZE.to_bytes(4, 'big')) 296 | # w.write(b'stco') 297 | # w.write((0).to_bytes(4, 'big')) # version and flags 298 | # w.write((0).to_bytes(4, 'big')) # entry count 299 | 300 | # Movie Extends Box 301 | w.write(pack('>I 4s', MVEXSIZE, b'mvex')) 302 | # w.write(MVEXSIZE.to_bytes(4, 'big')) 303 | # w.write(b'mvex') 304 | 305 | # Movie Extends Header Box 306 | w.write(pack('>I 4s I I', MEHDSIZE, b'mehd', 0 ,0)) 307 | # w.write(MEHDSIZE.to_bytes(4, 'big')) 308 | # w.write(b'mehd') 309 | # w.write((0).to_bytes(4, 'big')) # version and flags 310 | # w.write((0).to_bytes(4, 'big')) # fragment duration 311 | 312 | 313 | # Track Extends Box 314 | w.write(pack('>I 4s I I I I I I', TREXSIZE, b'trex', 0, 1, 1, 0, 0, 0x00010000)) 315 | # w.write(TREXSIZE.to_bytes(4, 'big')) 316 | # w.write(b'trex') 317 | # w.write((0).to_bytes(4, 'big')) # version and flags 318 | # w.write((1).to_bytes(4, 'big')) # track id 319 | # w.write((1).to_bytes(4, 'big')) # default sample description index 320 | # w.write((0).to_bytes(4, 'big')) # default sample duration 321 | # w.write((0).to_bytes(4, 'big')) # default sample size 322 | # w.write((0x00010000).to_bytes(4, 'big')) # default sample flags 323 | 324 | 325 | 326 | TFHDSIZE = 12 + 8 327 | TFDTSIZE = 12 + 8 328 | TRUNSIZE = 24 + 8 329 | TRAFSIZE = TFHDSIZE + TFDTSIZE + TRUNSIZE + 8 330 | MFHDSIZE = 8 + 8 331 | MOOFSIZE = MFHDSIZE + TRAFSIZE + 8 332 | 333 | # Movie Fragment Box 334 | def write_moof(w, seq, mdatsize, is_idr, sampleduration, decodetime): 335 | w.write(pack('>20s I 40s Q 20s 4s I I', 336 | b'\x00\x00\x00\x68\ 337 | moof\ 338 | \x00\x00\x00\x10\ 339 | mfhd\ 340 | \x00\x00\x00\x00', 341 | seq, 342 | b'\x00\x00\x00\x50\ 343 | traf\ 344 | \x00\x00\x00\x14\ 345 | tfhd\ 346 | \x00\x02\x00\x20\ 347 | \x00\x00\x00\x01\ 348 | \x01\x01\x00\x00\ 349 | \x00\x00\x00\x14\ 350 | tfdt\ 351 | \x01\x00\x00\x00', 352 | decodetime, 353 | b'\x00\x00\x00\x20\ 354 | trun\ 355 | \x00\x00\x03\x05\ 356 | \x00\x00\x00\x01\ 357 | \x00\x00\x00\x70', 358 | (b'\x02\x00\x00\x00' if is_idr else b'\x01\x01\x00\x00'), 359 | sampleduration, 360 | (mdatsize - 8))) 361 | 362 | # w.write(MOOFSIZE.to_bytes(4, 'big')) 363 | # w.write(b'moof') 364 | # 365 | # # Movie Fragment Header Box 366 | # w.write(MFHDSIZE.to_bytes(4, 'big')) 367 | # w.write(b'mfhd') 368 | # w.write((0).to_bytes(4, 'big')) # version and flags 369 | # w.write((seq).to_bytes(4, 'big')) # sequence number 370 | # 371 | # # Track Fragment Box 372 | # w.write((TRAFSIZE).to_bytes(4, 'big')) 373 | # w.write(b'traf') 374 | # 375 | # # Track Fragment Header Box 376 | # w.write((TFHDSIZE).to_bytes(4, 'big')) 377 | # w.write(b'tfhd') 378 | # w.write((0x020020).to_bytes(4, 'big')) # version and flags 379 | # w.write((1).to_bytes(4, 'big')) # track ID 380 | # w.write((0x01010000).to_bytes(4, 'big')) # default sample flags 381 | # 382 | # # Track Fragment Base Media Decode Time Box 383 | # w.write((TFDTSIZE).to_bytes(4, 'big')) 384 | # w.write(b'tfdt') 385 | # w.write((0x01000000).to_bytes(4, 'big')) # version and flags 386 | # w.write(decodetime.to_bytes(8, 'big')) # base media decode time 387 | # 388 | # # Track Run Box 389 | # w.write((TRUNSIZE).to_bytes(4, 'big')) 390 | # w.write(b'trun') 391 | # w.write((0x00000305).to_bytes(4, 'big')) # version and flags 392 | # w.write((1).to_bytes(4, 'big')) # sample count 393 | # w.write((0x70).to_bytes(4, 'big')) # data offset 394 | # if is_idr: 395 | # w.write((0x02000000).to_bytes(4, 'big')) # first sample flags (i-frame) 396 | # else: 397 | # w.write((0x01010000).to_bytes(4, 'big')) # first sample flags (not i-frame) 398 | # w.write((sampleduration).to_bytes(4, 'big')) # sample duration 399 | # w.write((mdatsize - 8).to_bytes(4, 'big')) # sample size 400 | 401 | # Media Data Box 402 | def write_mdat(w, nalus): 403 | w.write(get_mdat_size(nalus).to_bytes(4, 'big')) 404 | w.write(b'mdat') 405 | for nalu in nalus: 406 | w.write((len(nalu)).to_bytes(4, 'big')) 407 | w.write(nalu) 408 | 409 | def get_mdat_size(nalus): 410 | size = 8 411 | for nalu in nalus: 412 | size += 4+len(nalu) 413 | return size 414 | 415 | def test_h264_to_fmp4(h264file, mp4file): 416 | fin = open(h264file, 'rb') 417 | h264 = fin.read() 418 | fin.close() 419 | 420 | delim = b'\00\00\00\01' 421 | # Raspberry Pi 3B+ SPS/PPS for H.264 high 4.2 422 | sps = b'\x27\x64\x00\x2a\xac\x2b\x40\x28\x02\xdd\x00\xf1\x22\x6a' 423 | pps = b'\x28\xee\x02\x5c\xb0\x00' 424 | 425 | sampletime = 333 426 | timescale = 24*sampletime 427 | 428 | nals = h264.split(delim) 429 | 430 | fout = open(mp4file, 'wb') 431 | 432 | write_ftyp(fout) 433 | write_moov(fout, 1280, 720, 0, timescale, sps, pps) 434 | 435 | seq = 1 436 | for k, nal in enumerate(nals): 437 | if len(nal) == 0: 438 | continue 439 | nal_type = nal[0] & 0x1f 440 | if nal_type == 5 or nal_type == 1: 441 | if nal_type == 5: 442 | nalus = [nals[k-2], nals[k-1], nal] 443 | is_idr = True 444 | else: 445 | nalus = [nal] 446 | is_idr = False 447 | mdat_size = get_mdat_size(nalus) 448 | write_moof(fout, seq, mdat_size, is_idr, sampletime, seq*sampletime) 449 | write_mdat(fout, nalus) 450 | seq = seq + 1 451 | 452 | fout.close() 453 | -------------------------------------------------------------------------------- /v4l2.py: -------------------------------------------------------------------------------- 1 | # Python bindings for the v4l2 userspace api 2 | 3 | # Copyright (C) 1999-2009 the contributors 4 | 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # Alternatively you can redistribute this file under the terms of the 16 | # BSD license as stated below: 17 | 18 | # Redistribution and use in source and binary forms, with or without 19 | # modification, are permitted provided that the following conditions 20 | # are met: 21 | # 1. Redistributions of source code must retain the above copyright 22 | # notice, this list of conditions and the following disclaimer. 23 | # 2. Redistributions in binary form must reproduce the above copyright 24 | # notice, this list of conditions and the following disclaimer in 25 | # the documentation and/or other materials provided with the 26 | # distribution. 27 | # 3. The names of its contributors may not be used to endorse or promote 28 | # products derived from this software without specific prior written 29 | # permission. 30 | 31 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 32 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 33 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 34 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 35 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 36 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 37 | # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 38 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 39 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 40 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 41 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 | 43 | """ 44 | Python bindings for the v4l2 userspace api in Linux 2.6.34 45 | """ 46 | 47 | # see linux/videodev2.h 48 | 49 | import ctypes 50 | 51 | 52 | _IOC_NRBITS = 8 53 | _IOC_TYPEBITS = 8 54 | _IOC_SIZEBITS = 14 55 | _IOC_DIRBITS = 2 56 | 57 | _IOC_NRSHIFT = 0 58 | _IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS 59 | _IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS 60 | _IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS 61 | 62 | _IOC_NONE = 0 63 | _IOC_WRITE = 1 64 | _IOC_READ = 2 65 | 66 | 67 | def _IOC(dir_, type_, nr, size): 68 | return ( 69 | ctypes.c_int32(dir_ << _IOC_DIRSHIFT).value | 70 | ctypes.c_int32(ord(type_) << _IOC_TYPESHIFT).value | 71 | ctypes.c_int32(nr << _IOC_NRSHIFT).value | 72 | ctypes.c_int32(size << _IOC_SIZESHIFT).value) 73 | 74 | 75 | def _IOC_TYPECHECK(t): 76 | return ctypes.sizeof(t) 77 | 78 | 79 | def _IO(type_, nr): 80 | return _IOC(_IOC_NONE, type_, nr, 0) 81 | 82 | 83 | def _IOW(type_, nr, size): 84 | return _IOC(_IOC_WRITE, type_, nr, _IOC_TYPECHECK(size)) 85 | 86 | 87 | def _IOR(type_, nr, size): 88 | return _IOC(_IOC_READ, type_, nr, _IOC_TYPECHECK(size)) 89 | 90 | 91 | def _IOWR(type_, nr, size): 92 | return _IOC(_IOC_READ | _IOC_WRITE, type_, nr, _IOC_TYPECHECK(size)) 93 | 94 | 95 | # 96 | # type alias 97 | # 98 | 99 | enum = ctypes.c_uint 100 | c_int = ctypes.c_int 101 | 102 | 103 | # 104 | # time 105 | # 106 | 107 | class timeval(ctypes.Structure): 108 | _fields_ = [ 109 | ('secs', ctypes.c_long), 110 | ('usecs', ctypes.c_long), 111 | ] 112 | 113 | 114 | # 115 | # v4l2 116 | # 117 | 118 | 119 | VIDEO_MAX_FRAME = 32 120 | VIDEO_MAX_PLANES = 8 121 | 122 | 123 | VID_TYPE_CAPTURE = 1 124 | VID_TYPE_TUNER = 2 125 | VID_TYPE_TELETEXT = 4 126 | VID_TYPE_OVERLAY = 8 127 | VID_TYPE_CHROMAKEY = 16 128 | VID_TYPE_CLIPPING = 32 129 | VID_TYPE_FRAMERAM = 64 130 | VID_TYPE_SCALES = 128 131 | VID_TYPE_MONOCHROME = 256 132 | VID_TYPE_SUBCAPTURE = 512 133 | VID_TYPE_MPEG_DECODER = 1024 134 | VID_TYPE_MPEG_ENCODER = 2048 135 | VID_TYPE_MJPEG_DECODER = 4096 136 | VID_TYPE_MJPEG_ENCODER = 8192 137 | 138 | 139 | def v4l2_fourcc(a, b, c, d): 140 | return ord(a) | (ord(b) << 8) | (ord(c) << 16) | (ord(d) << 24) 141 | 142 | def get_fourcc(str): 143 | if len(str) != 4: 144 | raise ValueError(f'v4l2: Invalid fourcc: {str}') 145 | return v4l2_fourcc(str[0], str[1], str[2], str[3]) 146 | 147 | def get_mem_type(str): 148 | if str == 'MMAP': 149 | return V4L2_MEMORY_MMAP 150 | if str == 'DMABUF': 151 | return V4L2_MEMORY_DMABUF 152 | 153 | raise ValueError(f'v4l2: Invalid mem type: {str}, please use MMAP or DMABUF') 154 | 155 | v4l2_field = enum 156 | ( 157 | V4L2_FIELD_ANY, 158 | V4L2_FIELD_NONE, 159 | V4L2_FIELD_TOP, 160 | V4L2_FIELD_BOTTOM, 161 | V4L2_FIELD_INTERLACED, 162 | V4L2_FIELD_SEQ_TB, 163 | V4L2_FIELD_SEQ_BT, 164 | V4L2_FIELD_ALTERNATE, 165 | V4L2_FIELD_INTERLACED_TB, 166 | V4L2_FIELD_INTERLACED_BT, 167 | ) = range(10) 168 | 169 | 170 | def V4L2_FIELD_HAS_TOP(field): 171 | return ( 172 | field == V4L2_FIELD_TOP or 173 | field == V4L2_FIELD_INTERLACED or 174 | field == V4L2_FIELD_INTERLACED_TB or 175 | field == V4L2_FIELD_INTERLACED_BT or 176 | field == V4L2_FIELD_SEQ_TB or 177 | field == V4L2_FIELD_SEQ_BT) 178 | 179 | 180 | def V4L2_FIELD_HAS_BOTTOM(field): 181 | return ( 182 | field == V4L2_FIELD_BOTTOM or 183 | field == V4L2_FIELD_INTERLACED or 184 | field == V4L2_FIELD_INTERLACED_TB or 185 | field == V4L2_FIELD_INTERLACED_BT or 186 | field == V4L2_FIELD_SEQ_TB or 187 | field == V4L2_FIELD_SEQ_BT) 188 | 189 | 190 | def V4L2_FIELD_HAS_BOTH(field): 191 | return ( 192 | field == V4L2_FIELD_INTERLACED or 193 | field == V4L2_FIELD_INTERLACED_TB or 194 | field == V4L2_FIELD_INTERLACED_BT or 195 | field == V4L2_FIELD_SEQ_TB or 196 | field == V4L2_FIELD_SEQ_BT) 197 | 198 | 199 | v4l2_buf_type = enum 200 | ( 201 | V4L2_BUF_TYPE_VIDEO_CAPTURE, 202 | V4L2_BUF_TYPE_VIDEO_OUTPUT, 203 | V4L2_BUF_TYPE_VIDEO_OVERLAY, 204 | V4L2_BUF_TYPE_VBI_CAPTURE, 205 | V4L2_BUF_TYPE_VBI_OUTPUT, 206 | V4L2_BUF_TYPE_SLICED_VBI_CAPTURE, 207 | V4L2_BUF_TYPE_SLICED_VBI_OUTPUT, 208 | V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY, 209 | V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, 210 | V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, 211 | V4L2_BUF_TYPE_SDR_CAPTURE, 212 | V4L2_BUF_TYPE_SDR_OUTPUT, 213 | V4L2_BUF_TYPE_META_CAPTURE, 214 | V4L2_BUF_TYPE_META_OUTPUT, 215 | V4L2_BUF_TYPE_PRIVATE, 216 | ) = list(range(1, 15)) + [0x80] 217 | 218 | 219 | v4l2_ctrl_type = enum 220 | ( 221 | V4L2_CTRL_TYPE_INTEGER, 222 | V4L2_CTRL_TYPE_BOOLEAN, 223 | V4L2_CTRL_TYPE_MENU, 224 | V4L2_CTRL_TYPE_BUTTON, 225 | V4L2_CTRL_TYPE_INTEGER64, 226 | V4L2_CTRL_TYPE_CTRL_CLASS, 227 | V4L2_CTRL_TYPE_STRING, 228 | V4L2_CTRL_TYPE_BITMASK, 229 | V4L2_CTRL_TYPE_INTEGER_MENU, 230 | ) = range(1, 10) 231 | 232 | 233 | v4l2_tuner_type = enum 234 | ( 235 | V4L2_TUNER_RADIO, 236 | V4L2_TUNER_ANALOG_TV, 237 | V4L2_TUNER_DIGITAL_TV, 238 | ) = range(1, 4) 239 | 240 | 241 | v4l2_memory = enum 242 | ( 243 | V4L2_MEMORY_MMAP, 244 | V4L2_MEMORY_USERPTR, 245 | V4L2_MEMORY_OVERLAY, 246 | V4L2_MEMORY_DMABUF, 247 | ) = range(1, 5) 248 | 249 | 250 | v4l2_colorspace = enum 251 | ( 252 | V4L2_COLORSPACE_DEFAULT, 253 | V4L2_COLORSPACE_SMPTE170M, 254 | V4L2_COLORSPACE_SMPTE240M, 255 | V4L2_COLORSPACE_REC709, 256 | V4L2_COLORSPACE_BT878, 257 | V4L2_COLORSPACE_470_SYSTEM_M, 258 | V4L2_COLORSPACE_470_SYSTEM_BG, 259 | V4L2_COLORSPACE_JPEG, 260 | V4L2_COLORSPACE_SRGB, 261 | V4L2_COLORSPACE_OPRGB, 262 | V4L2_COLORSPACE_BT2020, 263 | V4L2_COLORSPACE_RAW, 264 | V4L2_COLORSPACE_DCI_P3, 265 | ) = range(13) 266 | 267 | 268 | v4l2_priority = enum 269 | ( 270 | V4L2_PRIORITY_UNSET, 271 | V4L2_PRIORITY_BACKGROUND, 272 | V4L2_PRIORITY_INTERACTIVE, 273 | V4L2_PRIORITY_RECORD, 274 | V4L2_PRIORITY_DEFAULT, 275 | ) = list(range(0, 4)) + [2] 276 | 277 | 278 | class v4l2_rect(ctypes.Structure): 279 | _fields_ = [ 280 | ('left', ctypes.c_int32), 281 | ('top', ctypes.c_int32), 282 | ('width', ctypes.c_int32), 283 | ('height', ctypes.c_int32), 284 | ] 285 | 286 | 287 | class v4l2_fract(ctypes.Structure): 288 | _fields_ = [ 289 | ('numerator', ctypes.c_uint32), 290 | ('denominator', ctypes.c_uint32), 291 | ] 292 | 293 | 294 | # 295 | # Driver capabilities 296 | # 297 | 298 | class v4l2_capability(ctypes.Structure): 299 | _fields_ = [ 300 | ('driver', ctypes.c_char * 16), 301 | ('card', ctypes.c_char * 32), 302 | ('bus_info', ctypes.c_char * 32), 303 | ('version', ctypes.c_uint32), 304 | ('capabilities', ctypes.c_uint32), 305 | ('reserved', ctypes.c_uint32 * 4), 306 | ] 307 | 308 | 309 | # 310 | # Values for 'capabilities' field 311 | # 312 | 313 | V4L2_CAP_VIDEO_CAPTURE = 0x00000001 314 | V4L2_CAP_VIDEO_OUTPUT = 0x00000002 315 | V4L2_CAP_VIDEO_OVERLAY = 0x00000004 316 | V4L2_CAP_VBI_CAPTURE = 0x00000010 317 | V4L2_CAP_VBI_OUTPUT = 0x00000020 318 | V4L2_CAP_SLICED_VBI_CAPTURE = 0x00000040 319 | V4L2_CAP_SLICED_VBI_OUTPUT = 0x00000080 320 | V4L2_CAP_RDS_CAPTURE = 0x00000100 321 | V4L2_CAP_VIDEO_OUTPUT_OVERLAY = 0x00000200 322 | V4L2_CAP_HW_FREQ_SEEK = 0x00000400 323 | V4L2_CAP_RDS_OUTPUT = 0x00000800 324 | 325 | V4L2_CAP_TUNER = 0x00010000 326 | V4L2_CAP_AUDIO = 0x00020000 327 | V4L2_CAP_RADIO = 0x00040000 328 | V4L2_CAP_MODULATOR = 0x00080000 329 | 330 | V4L2_CAP_READWRITE = 0x01000000 331 | V4L2_CAP_ASYNCIO = 0x02000000 332 | V4L2_CAP_STREAMING = 0x04000000 333 | 334 | 335 | # 336 | # Video image format 337 | # 338 | 339 | class v4l2_pix_format(ctypes.Structure): 340 | _fields_ = [ 341 | ('width', ctypes.c_uint32), 342 | ('height', ctypes.c_uint32), 343 | ('pixelformat', ctypes.c_uint32), 344 | ('field', v4l2_field), 345 | ('bytesperline', ctypes.c_uint32), 346 | ('sizeimage', ctypes.c_uint32), 347 | ('colorspace', v4l2_colorspace), 348 | ('priv', ctypes.c_uint32), 349 | ] 350 | 351 | class v4l2_plane_pix_format(ctypes.Structure): 352 | _fields_ = [ 353 | ('sizeimage', ctypes.c_uint32), 354 | ('bytesperline', ctypes.c_uint32), 355 | ('reserved', ctypes.c_uint16 * 6), 356 | ] 357 | _pack_ = True 358 | 359 | class v4l2_pix_format_mplane(ctypes.Structure): 360 | class _u(ctypes.Union): 361 | _fields_ = [ 362 | ('ycbcr_enc', ctypes.c_uint8), 363 | ('hsv_enc', ctypes.c_uint8), 364 | ] 365 | 366 | _fields_ = [ 367 | ('width', ctypes.c_uint32), 368 | ('height', ctypes.c_uint32), 369 | ('pixelformat', ctypes.c_uint32), 370 | ('field', v4l2_field), 371 | ('colorspace', v4l2_colorspace), 372 | ('plane_fmt', v4l2_plane_pix_format * VIDEO_MAX_PLANES), 373 | ('num_planes', ctypes.c_uint8), 374 | ('flags', ctypes.c_uint8), 375 | ('_u', _u), 376 | ('quantization', ctypes.c_uint8), 377 | ('xfer_func', ctypes.c_uint8), 378 | ('reserved', ctypes.c_uint8 * 7), 379 | ] 380 | 381 | _anonymous_ = ('_u',) 382 | _pack_ = True 383 | 384 | 385 | # RGB formats 386 | V4L2_PIX_FMT_RGB332 = v4l2_fourcc('R', 'G', 'B', '1') 387 | V4L2_PIX_FMT_RGB444 = v4l2_fourcc('R', '4', '4', '4') 388 | V4L2_PIX_FMT_RGB555 = v4l2_fourcc('R', 'G', 'B', 'O') 389 | V4L2_PIX_FMT_RGB565 = v4l2_fourcc('R', 'G', 'B', 'P') 390 | V4L2_PIX_FMT_RGB555X = v4l2_fourcc('R', 'G', 'B', 'Q') 391 | V4L2_PIX_FMT_RGB565X = v4l2_fourcc('R', 'G', 'B', 'R') 392 | V4L2_PIX_FMT_BGR24 = v4l2_fourcc('B', 'G', 'R', '3') 393 | V4L2_PIX_FMT_RGB24 = v4l2_fourcc('R', 'G', 'B', '3') 394 | V4L2_PIX_FMT_BGR32 = v4l2_fourcc('B', 'G', 'R', '4') 395 | V4L2_PIX_FMT_RGB32 = v4l2_fourcc('R', 'G', 'B', '4') 396 | 397 | # Grey formats 398 | V4L2_PIX_FMT_GREY = v4l2_fourcc('G', 'R', 'E', 'Y') 399 | V4L2_PIX_FMT_Y10 = v4l2_fourcc('Y', '1', '0', ' ') 400 | V4L2_PIX_FMT_Y16 = v4l2_fourcc('Y', '1', '6', ' ') 401 | 402 | # Palette formats 403 | V4L2_PIX_FMT_PAL8 = v4l2_fourcc('P', 'A', 'L', '8') 404 | 405 | # Luminance+Chrominance formats 406 | V4L2_PIX_FMT_YVU410 = v4l2_fourcc('Y', 'V', 'U', '9') 407 | V4L2_PIX_FMT_YVU420 = v4l2_fourcc('Y', 'V', '1', '2') 408 | V4L2_PIX_FMT_YUYV = v4l2_fourcc('Y', 'U', 'Y', 'V') 409 | V4L2_PIX_FMT_YYUV = v4l2_fourcc('Y', 'Y', 'U', 'V') 410 | V4L2_PIX_FMT_YVYU = v4l2_fourcc('Y', 'V', 'Y', 'U') 411 | V4L2_PIX_FMT_UYVY = v4l2_fourcc('U', 'Y', 'V', 'Y') 412 | V4L2_PIX_FMT_VYUY = v4l2_fourcc('V', 'Y', 'U', 'Y') 413 | V4L2_PIX_FMT_YUV422P = v4l2_fourcc('4', '2', '2', 'P') 414 | V4L2_PIX_FMT_YUV411P = v4l2_fourcc('4', '1', '1', 'P') 415 | V4L2_PIX_FMT_Y41P = v4l2_fourcc('Y', '4', '1', 'P') 416 | V4L2_PIX_FMT_YUV444 = v4l2_fourcc('Y', '4', '4', '4') 417 | V4L2_PIX_FMT_YUV555 = v4l2_fourcc('Y', 'U', 'V', 'O') 418 | V4L2_PIX_FMT_YUV565 = v4l2_fourcc('Y', 'U', 'V', 'P') 419 | V4L2_PIX_FMT_YUV32 = v4l2_fourcc('Y', 'U', 'V', '4') 420 | V4L2_PIX_FMT_YUV410 = v4l2_fourcc('Y', 'U', 'V', '9') 421 | V4L2_PIX_FMT_YUV420 = v4l2_fourcc('Y', 'U', '1', '2') 422 | V4L2_PIX_FMT_HI240 = v4l2_fourcc('H', 'I', '2', '4') 423 | V4L2_PIX_FMT_HM12 = v4l2_fourcc('H', 'M', '1', '2') 424 | 425 | # two planes -- one Y, one Cr + Cb interleaved 426 | V4L2_PIX_FMT_NV12 = v4l2_fourcc('N', 'V', '1', '2') 427 | V4L2_PIX_FMT_NV21 = v4l2_fourcc('N', 'V', '2', '1') 428 | V4L2_PIX_FMT_NV16 = v4l2_fourcc('N', 'V', '1', '6') 429 | V4L2_PIX_FMT_NV61 = v4l2_fourcc('N', 'V', '6', '1') 430 | 431 | # Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm 432 | V4L2_PIX_FMT_SBGGR8 = v4l2_fourcc('B', 'A', '8', '1') 433 | V4L2_PIX_FMT_SGBRG8 = v4l2_fourcc('G', 'B', 'R', 'G') 434 | V4L2_PIX_FMT_SGRBG8 = v4l2_fourcc('G', 'R', 'B', 'G') 435 | V4L2_PIX_FMT_SRGGB8 = v4l2_fourcc('R', 'G', 'G', 'B') 436 | V4L2_PIX_FMT_SBGGR10 = v4l2_fourcc('B', 'G', '1', '0') 437 | V4L2_PIX_FMT_SGBRG10 = v4l2_fourcc('G', 'B', '1', '0') 438 | V4L2_PIX_FMT_SGRBG10 = v4l2_fourcc('B', 'A', '1', '0') 439 | V4L2_PIX_FMT_SRGGB10 = v4l2_fourcc('R', 'G', '1', '0') 440 | V4L2_PIX_FMT_SGRBG10DPCM8 = v4l2_fourcc('B', 'D', '1', '0') 441 | V4L2_PIX_FMT_SBGGR16 = v4l2_fourcc('B', 'Y', 'R', '2') 442 | 443 | # compressed formats 444 | V4L2_PIX_FMT_MJPEG = v4l2_fourcc('M', 'J', 'P', 'G') 445 | V4L2_PIX_FMT_JPEG = v4l2_fourcc('J', 'P', 'E', 'G') 446 | V4L2_PIX_FMT_DV = v4l2_fourcc('d', 'v', 's', 'd') 447 | V4L2_PIX_FMT_MPEG = v4l2_fourcc('M', 'P', 'E', 'G') 448 | V4L2_PIX_FMT_H264 = v4l2_fourcc('H', '2', '6', '4') 449 | 450 | 451 | # Vendor-specific formats 452 | V4L2_PIX_FMT_CPIA1 = v4l2_fourcc('C', 'P', 'I', 'A') 453 | V4L2_PIX_FMT_WNVA = v4l2_fourcc('W', 'N', 'V', 'A') 454 | V4L2_PIX_FMT_SN9C10X = v4l2_fourcc('S', '9', '1', '0') 455 | V4L2_PIX_FMT_SN9C20X_I420 = v4l2_fourcc('S', '9', '2', '0') 456 | V4L2_PIX_FMT_PWC1 = v4l2_fourcc('P', 'W', 'C', '1') 457 | V4L2_PIX_FMT_PWC2 = v4l2_fourcc('P', 'W', 'C', '2') 458 | V4L2_PIX_FMT_ET61X251 = v4l2_fourcc('E', '6', '2', '5') 459 | V4L2_PIX_FMT_SPCA501 = v4l2_fourcc('S', '5', '0', '1') 460 | V4L2_PIX_FMT_SPCA505 = v4l2_fourcc('S', '5', '0', '5') 461 | V4L2_PIX_FMT_SPCA508 = v4l2_fourcc('S', '5', '0', '8') 462 | V4L2_PIX_FMT_SPCA561 = v4l2_fourcc('S', '5', '6', '1') 463 | V4L2_PIX_FMT_PAC207 = v4l2_fourcc('P', '2', '0', '7') 464 | V4L2_PIX_FMT_MR97310A = v4l2_fourcc('M', '3', '1', '0') 465 | V4L2_PIX_FMT_SN9C2028 = v4l2_fourcc('S', 'O', 'N', 'X') 466 | V4L2_PIX_FMT_SQ905C = v4l2_fourcc('9', '0', '5', 'C') 467 | V4L2_PIX_FMT_PJPG = v4l2_fourcc('P', 'J', 'P', 'G') 468 | V4L2_PIX_FMT_OV511 = v4l2_fourcc('O', '5', '1', '1') 469 | V4L2_PIX_FMT_OV518 = v4l2_fourcc('O', '5', '1', '8') 470 | V4L2_PIX_FMT_STV0680 = v4l2_fourcc('S', '6', '8', '0') 471 | 472 | 473 | # 474 | # Format enumeration 475 | # 476 | 477 | class v4l2_fmtdesc(ctypes.Structure): 478 | _fields_ = [ 479 | ('index', ctypes.c_uint32), 480 | ('type', ctypes.c_int), 481 | ('flags', ctypes.c_uint32), 482 | ('description', ctypes.c_char * 32), 483 | ('pixelformat', ctypes.c_uint32), 484 | ('reserved', ctypes.c_uint32 * 4), 485 | ] 486 | 487 | V4L2_FMT_FLAG_COMPRESSED = 0x0001 488 | V4L2_FMT_FLAG_EMULATED = 0x0002 489 | 490 | 491 | # 492 | # Experimental frame size and frame rate enumeration 493 | # 494 | 495 | v4l2_frmsizetypes = enum 496 | ( 497 | V4L2_FRMSIZE_TYPE_DISCRETE, 498 | V4L2_FRMSIZE_TYPE_CONTINUOUS, 499 | V4L2_FRMSIZE_TYPE_STEPWISE, 500 | ) = range(1, 4) 501 | 502 | 503 | class v4l2_frmsize_discrete(ctypes.Structure): 504 | _fields_ = [ 505 | ('width', ctypes.c_uint32), 506 | ('height', ctypes.c_uint32), 507 | ] 508 | 509 | 510 | class v4l2_frmsize_stepwise(ctypes.Structure): 511 | _fields_ = [ 512 | ('min_width', ctypes.c_uint32), 513 | ('max_width', ctypes.c_uint32), 514 | ('step_width', ctypes.c_uint32), 515 | ('min_height', ctypes.c_uint32), 516 | ('max_height', ctypes.c_uint32), 517 | ('step_height', ctypes.c_uint32), 518 | ] 519 | 520 | 521 | class v4l2_frmsizeenum(ctypes.Structure): 522 | class _u(ctypes.Union): 523 | _fields_ = [ 524 | ('discrete', v4l2_frmsize_discrete), 525 | ('stepwise', v4l2_frmsize_stepwise), 526 | ] 527 | 528 | _fields_ = [ 529 | ('index', ctypes.c_uint32), 530 | ('pixel_format', ctypes.c_uint32), 531 | ('type', ctypes.c_uint32), 532 | ('_u', _u), 533 | ('reserved', ctypes.c_uint32 * 2) 534 | ] 535 | 536 | _anonymous_ = ('_u',) 537 | 538 | 539 | # 540 | # Frame rate enumeration 541 | # 542 | 543 | v4l2_frmivaltypes = enum 544 | ( 545 | V4L2_FRMIVAL_TYPE_DISCRETE, 546 | V4L2_FRMIVAL_TYPE_CONTINUOUS, 547 | V4L2_FRMIVAL_TYPE_STEPWISE, 548 | ) = range(1, 4) 549 | 550 | 551 | class v4l2_frmival_stepwise(ctypes.Structure): 552 | _fields_ = [ 553 | ('min', v4l2_fract), 554 | ('max', v4l2_fract), 555 | ('step', v4l2_fract), 556 | ] 557 | 558 | 559 | class v4l2_frmivalenum(ctypes.Structure): 560 | class _u(ctypes.Union): 561 | _fields_ = [ 562 | ('discrete', v4l2_fract), 563 | ('stepwise', v4l2_frmival_stepwise), 564 | ] 565 | 566 | _fields_ = [ 567 | ('index', ctypes.c_uint32), 568 | ('pixel_format', ctypes.c_uint32), 569 | ('width', ctypes.c_uint32), 570 | ('height', ctypes.c_uint32), 571 | ('type', ctypes.c_uint32), 572 | ('_u', _u), 573 | ('reserved', ctypes.c_uint32 * 2), 574 | ] 575 | 576 | _anonymous_ = ('_u',) 577 | 578 | 579 | # 580 | # Timecode 581 | # 582 | 583 | class v4l2_timecode(ctypes.Structure): 584 | _fields_ = [ 585 | ('type', ctypes.c_uint32), 586 | ('flags', ctypes.c_uint32), 587 | ('frames', ctypes.c_uint8), 588 | ('seconds', ctypes.c_uint8), 589 | ('minutes', ctypes.c_uint8), 590 | ('hours', ctypes.c_uint8), 591 | ('userbits', ctypes.c_uint8 * 4), 592 | ] 593 | 594 | 595 | V4L2_TC_TYPE_24FPS = 1 596 | V4L2_TC_TYPE_25FPS = 2 597 | V4L2_TC_TYPE_30FPS = 3 598 | V4L2_TC_TYPE_50FPS = 4 599 | V4L2_TC_TYPE_60FPS = 5 600 | 601 | V4L2_TC_FLAG_DROPFRAME = 0x0001 602 | V4L2_TC_FLAG_COLORFRAME = 0x0002 603 | V4L2_TC_USERBITS_field = 0x000C 604 | V4L2_TC_USERBITS_USERDEFINED = 0x0000 605 | V4L2_TC_USERBITS_8BITCHARS = 0x0008 606 | 607 | 608 | class v4l2_jpegcompression(ctypes.Structure): 609 | _fields_ = [ 610 | ('quality', ctypes.c_int), 611 | ('APPn', ctypes.c_int), 612 | ('APP_len', ctypes.c_int), 613 | ('APP_data', ctypes.c_char * 60), 614 | ('COM_len', ctypes.c_int), 615 | ('COM_data', ctypes.c_char * 60), 616 | ('jpeg_markers', ctypes.c_uint32), 617 | ] 618 | 619 | 620 | V4L2_JPEG_MARKER_DHT = 1 << 3 621 | V4L2_JPEG_MARKER_DQT = 1 << 4 622 | V4L2_JPEG_MARKER_DRI = 1 << 5 623 | V4L2_JPEG_MARKER_COM = 1 << 6 624 | V4L2_JPEG_MARKER_APP = 1 << 7 625 | 626 | 627 | # 628 | # Memory-mapping buffers 629 | # 630 | 631 | class v4l2_requestbuffers(ctypes.Structure): 632 | _fields_ = [ 633 | ('count', ctypes.c_uint32), 634 | ('type', v4l2_buf_type), 635 | ('memory', v4l2_memory), 636 | ('reserved', ctypes.c_uint32 * 2), 637 | ] 638 | 639 | class v4l2_exportbuffer(ctypes.Structure): 640 | _fields_ = [ 641 | ('type', v4l2_buf_type), 642 | ('index', ctypes.c_uint32), 643 | ('plane', ctypes.c_uint32), 644 | ('flags', ctypes.c_uint32), 645 | ('fd', ctypes.c_int32), 646 | ('reserved', ctypes.c_uint32 * 11), 647 | ] 648 | 649 | 650 | class v4l2_plane(ctypes.Structure): 651 | class _u(ctypes.Union): 652 | _fields_ = [ 653 | ('mem_offset', ctypes.c_uint32), 654 | ('userptr', ctypes.c_ulong), 655 | ('fd', ctypes.c_int32), 656 | ] 657 | 658 | _fields_ = [ 659 | ('bytesused', ctypes.c_uint32), 660 | ('length', ctypes.c_uint32), 661 | ('m', _u), 662 | ('data_offset', ctypes.c_uint32), 663 | ('reserved', ctypes.c_uint32 * 11), 664 | ] 665 | 666 | v4l2_planes = v4l2_plane * VIDEO_MAX_PLANES 667 | 668 | class v4l2_buffer(ctypes.Structure): 669 | class _u(ctypes.Union): 670 | _fields_ = [ 671 | ('offset', ctypes.c_uint32), 672 | ('userptr', ctypes.c_ulong), 673 | ('planes', ctypes.POINTER(v4l2_plane)), 674 | ('fd', ctypes.c_int32), 675 | ] 676 | 677 | _fields_ = [ 678 | ('index', ctypes.c_uint32), 679 | ('type', v4l2_buf_type), 680 | ('bytesused', ctypes.c_uint32), 681 | ('flags', ctypes.c_uint32), 682 | ('field', v4l2_field), 683 | ('timestamp', timeval), 684 | ('timecode', v4l2_timecode), 685 | ('sequence', ctypes.c_uint32), 686 | ('memory', v4l2_memory), 687 | ('m', _u), 688 | ('length', ctypes.c_uint32), 689 | ('input', ctypes.c_uint32), 690 | ('reserved', ctypes.c_uint32), 691 | ] 692 | 693 | 694 | V4L2_BUF_FLAG_MAPPED = 0x0001 695 | V4L2_BUF_FLAG_QUEUED = 0x0002 696 | V4L2_BUF_FLAG_DONE = 0x0004 697 | V4L2_BUF_FLAG_KEYFRAME = 0x0008 698 | V4L2_BUF_FLAG_PFRAME = 0x0010 699 | V4L2_BUF_FLAG_BFRAME = 0x0020 700 | V4L2_BUF_FLAG_ERROR = 0x0040 701 | V4L2_BUF_FLAG_TIMECODE = 0x0100 702 | V4L2_BUF_FLAG_INPUT = 0x0200 703 | 704 | 705 | # 706 | # Overlay preview 707 | # 708 | 709 | class v4l2_framebuffer(ctypes.Structure): 710 | _fields_ = [ 711 | ('capability', ctypes.c_uint32), 712 | ('flags', ctypes.c_uint32), 713 | ('base', ctypes.c_void_p), 714 | ('fmt', v4l2_pix_format), 715 | ] 716 | 717 | V4L2_FBUF_CAP_EXTERNOVERLAY = 0x0001 718 | V4L2_FBUF_CAP_CHROMAKEY = 0x0002 719 | V4L2_FBUF_CAP_LIST_CLIPPING = 0x0004 720 | V4L2_FBUF_CAP_BITMAP_CLIPPING = 0x0008 721 | V4L2_FBUF_CAP_LOCAL_ALPHA = 0x0010 722 | V4L2_FBUF_CAP_GLOBAL_ALPHA = 0x0020 723 | V4L2_FBUF_CAP_LOCAL_INV_ALPHA = 0x0040 724 | V4L2_FBUF_CAP_SRC_CHROMAKEY = 0x0080 725 | 726 | V4L2_FBUF_FLAG_PRIMARY = 0x0001 727 | V4L2_FBUF_FLAG_OVERLAY = 0x0002 728 | V4L2_FBUF_FLAG_CHROMAKEY = 0x0004 729 | V4L2_FBUF_FLAG_LOCAL_ALPHA = 0x0008 730 | V4L2_FBUF_FLAG_GLOBAL_ALPHA = 0x0010 731 | V4L2_FBUF_FLAG_LOCAL_INV_ALPHA = 0x0020 732 | V4L2_FBUF_FLAG_SRC_CHROMAKEY = 0x0040 733 | 734 | 735 | class v4l2_clip(ctypes.Structure): 736 | pass 737 | v4l2_clip._fields_ = [ 738 | ('c', v4l2_rect), 739 | ('next', ctypes.POINTER(v4l2_clip)), 740 | ] 741 | 742 | 743 | class v4l2_window(ctypes.Structure): 744 | _fields_ = [ 745 | ('w', v4l2_rect), 746 | ('field', v4l2_field), 747 | ('chromakey', ctypes.c_uint32), 748 | ('clips', ctypes.POINTER(v4l2_clip)), 749 | ('clipcount', ctypes.c_uint32), 750 | ('bitmap', ctypes.c_void_p), 751 | ('global_alpha', ctypes.c_uint8), 752 | ] 753 | 754 | 755 | # 756 | # Capture parameters 757 | # 758 | 759 | class v4l2_captureparm(ctypes.Structure): 760 | _fields_ = [ 761 | ('capability', ctypes.c_uint32), 762 | ('capturemode', ctypes.c_uint32), 763 | ('timeperframe', v4l2_fract), 764 | ('extendedmode', ctypes.c_uint32), 765 | ('readbuffers', ctypes.c_uint32), 766 | ('reserved', ctypes.c_uint32 * 4), 767 | ] 768 | 769 | 770 | V4L2_MODE_HIGHQUALITY = 0x0001 771 | V4L2_CAP_TIMEPERFRAME = 0x1000 772 | 773 | 774 | class v4l2_outputparm(ctypes.Structure): 775 | _fields_ = [ 776 | ('capability', ctypes.c_uint32), 777 | ('outputmode', ctypes.c_uint32), 778 | ('timeperframe', v4l2_fract), 779 | ('extendedmode', ctypes.c_uint32), 780 | ('writebuffers', ctypes.c_uint32), 781 | ('reserved', ctypes.c_uint32 * 4), 782 | ] 783 | 784 | 785 | # 786 | # Input image cropping 787 | # 788 | 789 | class v4l2_cropcap(ctypes.Structure): 790 | _fields_ = [ 791 | ('type', v4l2_buf_type), 792 | ('bounds', v4l2_rect), 793 | ('defrect', v4l2_rect), 794 | ('pixelaspect', v4l2_fract), 795 | ] 796 | 797 | 798 | class v4l2_crop(ctypes.Structure): 799 | _fields_ = [ 800 | ('type', ctypes.c_int), 801 | ('c', v4l2_rect), 802 | ] 803 | 804 | class v4l2_selection(ctypes.Structure): 805 | _fields_ = [ 806 | ('type', ctypes.c_uint32), 807 | ('target', ctypes.c_uint32), 808 | ('flags', ctypes.c_uint32), 809 | ('r', v4l2_rect), 810 | ('reserverd', ctypes.c_uint32 * 9), 811 | ] 812 | 813 | V4L2_SEL_TGT_CROP = 0x0000 814 | V4L2_SEL_TGT_CROP_DEFAULT = 0x0001 815 | V4L2_SEL_TGT_CROP_BOUNDS = 0x0002 816 | V4L2_SEL_TGT_NATIVE_SIZE = 0x0003 817 | V4L2_SEL_TGT_COMPOSE = 0x0100 818 | V4L2_SEL_TGT_COMPOSE_DEFAULT = 0x0101 819 | V4L2_SEL_TGT_COMPOSE_BOUNDS = 0x0102 820 | V4L2_SEL_TGT_COMPOSE_PADDED = 0x0103 821 | 822 | V4L2_SEL_FLAG_GE = (1 << 0) 823 | V4L2_SEL_FLAG_LE = (1 << 1) 824 | V4L2_SEL_FLAG_KEEP_CONFIG = (1 << 2) 825 | 826 | 827 | # 828 | # Analog video standard 829 | # 830 | 831 | v4l2_std_id = ctypes.c_uint64 832 | 833 | 834 | V4L2_STD_PAL_B = 0x00000001 835 | V4L2_STD_PAL_B1 = 0x00000002 836 | V4L2_STD_PAL_G = 0x00000004 837 | V4L2_STD_PAL_H = 0x00000008 838 | V4L2_STD_PAL_I = 0x00000010 839 | V4L2_STD_PAL_D = 0x00000020 840 | V4L2_STD_PAL_D1 = 0x00000040 841 | V4L2_STD_PAL_K = 0x00000080 842 | 843 | V4L2_STD_PAL_M = 0x00000100 844 | V4L2_STD_PAL_N = 0x00000200 845 | V4L2_STD_PAL_Nc = 0x00000400 846 | V4L2_STD_PAL_60 = 0x00000800 847 | 848 | V4L2_STD_NTSC_M = 0x00001000 849 | V4L2_STD_NTSC_M_JP = 0x00002000 850 | V4L2_STD_NTSC_443 = 0x00004000 851 | V4L2_STD_NTSC_M_KR = 0x00008000 852 | 853 | V4L2_STD_SECAM_B = 0x00010000 854 | V4L2_STD_SECAM_D = 0x00020000 855 | V4L2_STD_SECAM_G = 0x00040000 856 | V4L2_STD_SECAM_H = 0x00080000 857 | V4L2_STD_SECAM_K = 0x00100000 858 | V4L2_STD_SECAM_K1 = 0x00200000 859 | V4L2_STD_SECAM_L = 0x00400000 860 | V4L2_STD_SECAM_LC = 0x00800000 861 | 862 | V4L2_STD_ATSC_8_VSB = 0x01000000 863 | V4L2_STD_ATSC_16_VSB = 0x02000000 864 | 865 | 866 | # some common needed stuff 867 | V4L2_STD_PAL_BG = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_PAL_G) 868 | V4L2_STD_PAL_DK = (V4L2_STD_PAL_D | V4L2_STD_PAL_D1 | V4L2_STD_PAL_K) 869 | V4L2_STD_PAL = (V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_H | V4L2_STD_PAL_I) 870 | V4L2_STD_NTSC = (V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_M_KR) 871 | V4L2_STD_SECAM_DK = (V4L2_STD_SECAM_D | V4L2_STD_SECAM_K | V4L2_STD_SECAM_K1) 872 | V4L2_STD_SECAM = (V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H | V4L2_STD_SECAM_DK | V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC) 873 | 874 | V4L2_STD_525_60 = (V4L2_STD_PAL_M | V4L2_STD_PAL_60 | V4L2_STD_NTSC | V4L2_STD_NTSC_443) 875 | V4L2_STD_625_50 = (V4L2_STD_PAL | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_SECAM) 876 | V4L2_STD_ATSC = (V4L2_STD_ATSC_8_VSB | V4L2_STD_ATSC_16_VSB) 877 | 878 | V4L2_STD_UNKNOWN = 0 879 | V4L2_STD_ALL = (V4L2_STD_525_60 | V4L2_STD_625_50) 880 | 881 | # some merged standards 882 | V4L2_STD_MN = (V4L2_STD_PAL_M | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_NTSC) 883 | V4L2_STD_B = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_SECAM_B) 884 | V4L2_STD_GH = (V4L2_STD_PAL_G | V4L2_STD_PAL_H|V4L2_STD_SECAM_G | V4L2_STD_SECAM_H) 885 | V4L2_STD_DK = (V4L2_STD_PAL_DK | V4L2_STD_SECAM_DK) 886 | 887 | 888 | class v4l2_standard(ctypes.Structure): 889 | _fields_ = [ 890 | ('index', ctypes.c_uint32), 891 | ('id', v4l2_std_id), 892 | ('name', ctypes.c_char * 24), 893 | ('frameperiod', v4l2_fract), 894 | ('framelines', ctypes.c_uint32), 895 | ('reserved', ctypes.c_uint32 * 4), 896 | ] 897 | 898 | 899 | # 900 | # Video timings dv preset 901 | # 902 | 903 | class v4l2_dv_preset(ctypes.Structure): 904 | _fields_ = [ 905 | ('preset', ctypes.c_uint32), 906 | ('reserved', ctypes.c_uint32 * 4) 907 | ] 908 | 909 | 910 | # 911 | # DV preset enumeration 912 | # 913 | 914 | class v4l2_dv_enum_preset(ctypes.Structure): 915 | _fields_ = [ 916 | ('index', ctypes.c_uint32), 917 | ('preset', ctypes.c_uint32), 918 | ('name', ctypes.c_char * 32), 919 | ('width', ctypes.c_uint32), 920 | ('height', ctypes.c_uint32), 921 | ('reserved', ctypes.c_uint32 * 4), 922 | ] 923 | 924 | # 925 | # DV preset values 926 | # 927 | 928 | V4L2_DV_INVALID = 0 929 | V4L2_DV_480P59_94 = 1 930 | V4L2_DV_576P50 = 2 931 | V4L2_DV_720P24 = 3 932 | V4L2_DV_720P25 = 4 933 | V4L2_DV_720P30 = 5 934 | V4L2_DV_720P50 = 6 935 | V4L2_DV_720P59_94 = 7 936 | V4L2_DV_720P60 = 8 937 | V4L2_DV_1080I29_97 = 9 938 | V4L2_DV_1080I30 = 10 939 | V4L2_DV_1080I25 = 11 940 | V4L2_DV_1080I50 = 12 941 | V4L2_DV_1080I60 = 13 942 | V4L2_DV_1080P24 = 14 943 | V4L2_DV_1080P25 = 15 944 | V4L2_DV_1080P30 = 16 945 | V4L2_DV_1080P50 = 17 946 | V4L2_DV_1080P60 = 18 947 | 948 | 949 | # 950 | # DV BT timings 951 | # 952 | 953 | class v4l2_bt_timings(ctypes.Structure): 954 | _fields_ = [ 955 | ('width', ctypes.c_uint32), 956 | ('height', ctypes.c_uint32), 957 | ('interlaced', ctypes.c_uint32), 958 | ('polarities', ctypes.c_uint32), 959 | ('pixelclock', ctypes.c_uint64), 960 | ('hfrontporch', ctypes.c_uint32), 961 | ('hsync', ctypes.c_uint32), 962 | ('hbackporch', ctypes.c_uint32), 963 | ('vfrontporch', ctypes.c_uint32), 964 | ('vsync', ctypes.c_uint32), 965 | ('vbackporch', ctypes.c_uint32), 966 | ('il_vfrontporch', ctypes.c_uint32), 967 | ('il_vsync', ctypes.c_uint32), 968 | ('il_vbackporch', ctypes.c_uint32), 969 | ('reserved', ctypes.c_uint32 * 16), 970 | ] 971 | 972 | _pack_ = True 973 | 974 | # Interlaced or progressive format 975 | V4L2_DV_PROGRESSIVE = 0 976 | V4L2_DV_INTERLACED = 1 977 | 978 | # Polarities. If bit is not set, it is assumed to be negative polarity 979 | V4L2_DV_VSYNC_POS_POL = 0x00000001 980 | V4L2_DV_HSYNC_POS_POL = 0x00000002 981 | 982 | 983 | class v4l2_dv_timings(ctypes.Structure): 984 | class _u(ctypes.Union): 985 | _fields_ = [ 986 | ('bt', v4l2_bt_timings), 987 | ('reserved', ctypes.c_uint32 * 32), 988 | ] 989 | 990 | _fields_ = [ 991 | ('type', ctypes.c_uint32), 992 | ('_u', _u), 993 | ] 994 | 995 | _anonymous_ = ('_u',) 996 | _pack_ = True 997 | 998 | 999 | # Values for the type field 1000 | V4L2_DV_BT_656_1120 = 0 1001 | 1002 | 1003 | # 1004 | # Video inputs 1005 | # 1006 | 1007 | class v4l2_input(ctypes.Structure): 1008 | _fields_ = [ 1009 | ('index', ctypes.c_uint32), 1010 | ('name', ctypes.c_char * 32), 1011 | ('type', ctypes.c_uint32), 1012 | ('audioset', ctypes.c_uint32), 1013 | ('tuner', ctypes.c_uint32), 1014 | ('std', v4l2_std_id), 1015 | ('status', ctypes.c_uint32), 1016 | ('reserved', ctypes.c_uint32 * 4), 1017 | ] 1018 | 1019 | 1020 | V4L2_INPUT_TYPE_TUNER = 1 1021 | V4L2_INPUT_TYPE_CAMERA = 2 1022 | 1023 | V4L2_IN_ST_NO_POWER = 0x00000001 1024 | V4L2_IN_ST_NO_SIGNAL = 0x00000002 1025 | V4L2_IN_ST_NO_COLOR = 0x00000004 1026 | 1027 | V4L2_IN_ST_HFLIP = 0x00000010 1028 | V4L2_IN_ST_VFLIP = 0x00000020 1029 | 1030 | V4L2_IN_ST_NO_H_LOCK = 0x00000100 1031 | V4L2_IN_ST_COLOR_KILL = 0x00000200 1032 | 1033 | V4L2_IN_ST_NO_SYNC = 0x00010000 1034 | V4L2_IN_ST_NO_EQU = 0x00020000 1035 | V4L2_IN_ST_NO_CARRIER = 0x00040000 1036 | 1037 | V4L2_IN_ST_MACROVISION = 0x01000000 1038 | V4L2_IN_ST_NO_ACCESS = 0x02000000 1039 | V4L2_IN_ST_VTR = 0x04000000 1040 | 1041 | V4L2_IN_CAP_PRESETS = 0x00000001 1042 | V4L2_IN_CAP_CUSTOM_TIMINGS = 0x00000002 1043 | V4L2_IN_CAP_STD = 0x00000004 1044 | 1045 | # 1046 | # Video outputs 1047 | # 1048 | 1049 | class v4l2_output(ctypes.Structure): 1050 | _fields_ = [ 1051 | ('index', ctypes.c_uint32), 1052 | ('name', ctypes.c_char * 32), 1053 | ('type', ctypes.c_uint32), 1054 | ('audioset', ctypes.c_uint32), 1055 | ('modulator', ctypes.c_uint32), 1056 | ('std', v4l2_std_id), 1057 | ('reserved', ctypes.c_uint32 * 4), 1058 | ] 1059 | 1060 | 1061 | V4L2_OUTPUT_TYPE_MODULATOR = 1 1062 | V4L2_OUTPUT_TYPE_ANALOG = 2 1063 | V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY = 3 1064 | 1065 | V4L2_OUT_CAP_PRESETS = 0x00000001 1066 | V4L2_OUT_CAP_CUSTOM_TIMINGS = 0x00000002 1067 | V4L2_OUT_CAP_STD = 0x00000004 1068 | 1069 | # 1070 | # Controls 1071 | # 1072 | 1073 | class v4l2_control(ctypes.Structure): 1074 | _fields_ = [ 1075 | ('id', ctypes.c_uint32), 1076 | ('value', ctypes.c_int32), 1077 | ] 1078 | 1079 | 1080 | class v4l2_ext_control(ctypes.Structure): 1081 | class _u(ctypes.Union): 1082 | _fields_ = [ 1083 | ('value', ctypes.c_int32), 1084 | ('value64', ctypes.c_int64), 1085 | ('reserved', ctypes.c_void_p), 1086 | ] 1087 | 1088 | _fields_ = [ 1089 | ('id', ctypes.c_uint32), 1090 | ('reserved2', ctypes.c_uint32 * 2), 1091 | ('_u', _u) 1092 | ] 1093 | 1094 | _anonymous_ = ('_u',) 1095 | _pack_ = True 1096 | 1097 | 1098 | class v4l2_ext_controls(ctypes.Structure): 1099 | _fields_ = [ 1100 | ('ctrl_class', ctypes.c_uint32), 1101 | ('count', ctypes.c_uint32), 1102 | ('error_idx', ctypes.c_uint32), 1103 | ('reserved', ctypes.c_uint32 * 2), 1104 | ('controls', ctypes.POINTER(v4l2_ext_control)), 1105 | ] 1106 | 1107 | 1108 | V4L2_CTRL_CLASS_USER = 0x00980000 1109 | V4L2_CTRL_CLASS_CODEC = 0x00990000 1110 | V4L2_CTRL_CLASS_CAMERA = 0x009a0000 1111 | V4L2_CTRL_CLASS_FM_TX = 0x009b0000 1112 | 1113 | 1114 | def V4L2_CTRL_ID_MASK(): 1115 | return 0x0fffffff 1116 | 1117 | 1118 | def V4L2_CTRL_ID2CLASS(id_): 1119 | return id_ & 0x0fff0000 # unsigned long 1120 | 1121 | 1122 | def V4L2_CTRL_DRIVER_PRIV(id_): 1123 | return (id_ & 0xffff) >= 0x1000 1124 | 1125 | 1126 | class v4l2_queryctrl(ctypes.Structure): 1127 | _fields_ = [ 1128 | ('id', ctypes.c_uint32), 1129 | ('type', v4l2_ctrl_type), 1130 | ('name', ctypes.c_char * 32), 1131 | ('minimum', ctypes.c_int32), 1132 | ('maximum', ctypes.c_int32), 1133 | ('step', ctypes.c_int32), 1134 | ('default', ctypes.c_int32), 1135 | ('flags', ctypes.c_uint32), 1136 | ('reserved', ctypes.c_uint32 * 2), 1137 | ] 1138 | 1139 | 1140 | class v4l2_querymenu(ctypes.Structure): 1141 | class _u(ctypes.Union): 1142 | _fields_ = [ 1143 | ('name', ctypes.c_char * 32), 1144 | ('value', ctypes.c_int64), 1145 | ] 1146 | 1147 | _fields_ = [ 1148 | ('id', ctypes.c_uint32), 1149 | ('index', ctypes.c_uint32), 1150 | ('_u', _u), 1151 | ('reserved', ctypes.c_uint32), 1152 | ] 1153 | _anonymous_ = ('_u',) 1154 | _pack_ = True 1155 | 1156 | 1157 | V4L2_CTRL_FLAG_DISABLED = 0x0001 1158 | V4L2_CTRL_FLAG_GRABBED = 0x0002 1159 | V4L2_CTRL_FLAG_READ_ONLY = 0x0004 1160 | V4L2_CTRL_FLAG_UPDATE = 0x0008 1161 | V4L2_CTRL_FLAG_INACTIVE = 0x0010 1162 | V4L2_CTRL_FLAG_SLIDER = 0x0020 1163 | V4L2_CTRL_FLAG_WRITE_ONLY = 0x0040 1164 | 1165 | V4L2_CTRL_FLAG_NEXT_CTRL = 0x80000000 1166 | V4L2_CTRL_FLAG_NEXT_COMPOUND = 0x40000000 1167 | 1168 | V4L2_CID_BASE = V4L2_CTRL_CLASS_USER | 0x900 1169 | V4L2_CID_USER_BASE = V4L2_CID_BASE 1170 | V4L2_CID_PRIVATE_BASE = 0x08000000 1171 | 1172 | V4L2_CID_USER_CLASS = V4L2_CTRL_CLASS_USER | 1 1173 | V4L2_CID_BRIGHTNESS = V4L2_CID_BASE + 0 1174 | V4L2_CID_CONTRAST = V4L2_CID_BASE + 1 1175 | V4L2_CID_SATURATION = V4L2_CID_BASE + 2 1176 | V4L2_CID_HUE = V4L2_CID_BASE + 3 1177 | V4L2_CID_AUDIO_VOLUME = V4L2_CID_BASE + 5 1178 | V4L2_CID_AUDIO_BALANCE = V4L2_CID_BASE + 6 1179 | V4L2_CID_AUDIO_BASS = V4L2_CID_BASE + 7 1180 | V4L2_CID_AUDIO_TREBLE = V4L2_CID_BASE + 8 1181 | V4L2_CID_AUDIO_MUTE = V4L2_CID_BASE + 9 1182 | V4L2_CID_AUDIO_LOUDNESS = V4L2_CID_BASE + 10 1183 | V4L2_CID_BLACK_LEVEL = V4L2_CID_BASE + 11 # Deprecated 1184 | V4L2_CID_AUTO_WHITE_BALANCE = V4L2_CID_BASE + 12 1185 | V4L2_CID_DO_WHITE_BALANCE = V4L2_CID_BASE + 13 1186 | V4L2_CID_RED_BALANCE = V4L2_CID_BASE + 14 1187 | V4L2_CID_BLUE_BALANCE = V4L2_CID_BASE + 15 1188 | V4L2_CID_GAMMA = V4L2_CID_BASE + 16 1189 | V4L2_CID_WHITENESS = V4L2_CID_GAMMA # Deprecated 1190 | V4L2_CID_EXPOSURE = V4L2_CID_BASE + 17 1191 | V4L2_CID_AUTOGAIN = V4L2_CID_BASE + 18 1192 | V4L2_CID_GAIN = V4L2_CID_BASE + 19 1193 | V4L2_CID_HFLIP = V4L2_CID_BASE + 20 1194 | V4L2_CID_VFLIP = V4L2_CID_BASE + 21 1195 | 1196 | # Deprecated; use V4L2_CID_PAN_RESET and V4L2_CID_TILT_RESET 1197 | V4L2_CID_HCENTER = V4L2_CID_BASE + 22 1198 | V4L2_CID_VCENTER = V4L2_CID_BASE + 23 1199 | 1200 | V4L2_CID_POWER_LINE_FREQUENCY = V4L2_CID_BASE + 24 1201 | 1202 | v4l2_power_line_frequency = enum 1203 | ( 1204 | V4L2_CID_POWER_LINE_FREQUENCY_DISABLED, 1205 | V4L2_CID_POWER_LINE_FREQUENCY_50HZ, 1206 | V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 1207 | ) = range(3) 1208 | 1209 | V4L2_CID_HUE_AUTO = V4L2_CID_BASE + 25 1210 | V4L2_CID_WHITE_BALANCE_TEMPERATURE = V4L2_CID_BASE + 26 1211 | V4L2_CID_SHARPNESS = V4L2_CID_BASE + 27 1212 | V4L2_CID_BACKLIGHT_COMPENSATION = V4L2_CID_BASE + 28 1213 | V4L2_CID_CHROMA_AGC = V4L2_CID_BASE + 29 1214 | V4L2_CID_COLOR_KILLER = V4L2_CID_BASE + 30 1215 | V4L2_CID_COLORFX = V4L2_CID_BASE + 31 1216 | 1217 | v4l2_colorfx = enum 1218 | ( 1219 | V4L2_COLORFX_NONE, 1220 | V4L2_COLORFX_BW, 1221 | V4L2_COLORFX_SEPIA, 1222 | ) = range(3) 1223 | 1224 | V4L2_CID_AUTOBRIGHTNESS = V4L2_CID_BASE + 32 1225 | V4L2_CID_BAND_STOP_FILTER = V4L2_CID_BASE + 33 1226 | 1227 | V4L2_CID_ROTATE = V4L2_CID_BASE + 34 1228 | V4L2_CID_BG_COLOR = V4L2_CID_BASE + 35 1229 | V4L2_CID_CHROMA_GAIN = V4L2_CID_BASE + 36 1230 | V4L2_CID_ILLUMINATORS_1 = V4L2_CID_BASE + 37 1231 | V4L2_CID_ILLUMINATORS_2 = V4L2_CID_BASE + 38 1232 | V4L2_CID_MIN_BUFFERS_FOR_CAPTURE = V4L2_CID_BASE + 39 1233 | V4L2_CID_MIN_BUFFERS_FOR_OUTPUT = V4L2_CID_BASE + 40 1234 | V4L2_CID_ALPHA_COMPONENT = V4L2_CID_BASE + 41 1235 | V4L2_CID_COLORFX_CBCR = V4L2_CID_BASE + 42 1236 | V4L2_CID_LASTP1 = V4L2_CID_BASE + 43 1237 | 1238 | V4L2_CID_CODEC_BASE = V4L2_CTRL_CLASS_CODEC | 0x900 1239 | V4L2_CID_CODEC_CLASS = V4L2_CTRL_CLASS_CODEC | 1 1240 | 1241 | # MPEG streams 1242 | V4L2_CID_MPEG_STREAM_TYPE = V4L2_CID_CODEC_BASE + 0 1243 | 1244 | v4l2_mpeg_stream_type = enum 1245 | ( 1246 | V4L2_MPEG_STREAM_TYPE_MPEG2_PS, 1247 | V4L2_MPEG_STREAM_TYPE_MPEG2_TS, 1248 | V4L2_MPEG_STREAM_TYPE_MPEG1_SS, 1249 | V4L2_MPEG_STREAM_TYPE_MPEG2_DVD, 1250 | V4L2_MPEG_STREAM_TYPE_MPEG1_VCD, 1251 | V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD, 1252 | ) = range(6) 1253 | 1254 | V4L2_CID_MPEG_STREAM_PID_PMT = V4L2_CID_CODEC_BASE + 1 1255 | V4L2_CID_MPEG_STREAM_PID_AUDIO = V4L2_CID_CODEC_BASE + 2 1256 | V4L2_CID_MPEG_STREAM_PID_VIDEO = V4L2_CID_CODEC_BASE + 3 1257 | V4L2_CID_MPEG_STREAM_PID_PCR = V4L2_CID_CODEC_BASE + 4 1258 | V4L2_CID_MPEG_STREAM_PES_ID_AUDIO = V4L2_CID_CODEC_BASE + 5 1259 | V4L2_CID_MPEG_STREAM_PES_ID_VIDEO = V4L2_CID_CODEC_BASE + 6 1260 | V4L2_CID_MPEG_STREAM_VBI_FMT = V4L2_CID_CODEC_BASE + 7 1261 | 1262 | v4l2_mpeg_stream_vbi_fmt = enum 1263 | ( 1264 | V4L2_MPEG_STREAM_VBI_FMT_NONE, 1265 | V4L2_MPEG_STREAM_VBI_FMT_IVTV, 1266 | ) = range(2) 1267 | 1268 | V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ = V4L2_CID_CODEC_BASE + 100 1269 | 1270 | v4l2_mpeg_audio_sampling_freq = enum 1271 | ( 1272 | V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100, 1273 | V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000, 1274 | V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000, 1275 | ) = range(3) 1276 | 1277 | V4L2_CID_MPEG_AUDIO_ENCODING = V4L2_CID_CODEC_BASE + 101 1278 | 1279 | v4l2_mpeg_audio_encoding = enum 1280 | ( 1281 | V4L2_MPEG_AUDIO_ENCODING_LAYER_1, 1282 | V4L2_MPEG_AUDIO_ENCODING_LAYER_2, 1283 | V4L2_MPEG_AUDIO_ENCODING_LAYER_3, 1284 | V4L2_MPEG_AUDIO_ENCODING_AAC, 1285 | V4L2_MPEG_AUDIO_ENCODING_AC3, 1286 | ) = range(5) 1287 | 1288 | V4L2_CID_MPEG_AUDIO_L1_BITRATE = V4L2_CID_CODEC_BASE + 102 1289 | 1290 | v4l2_mpeg_audio_l1_bitrate = enum 1291 | ( 1292 | V4L2_MPEG_AUDIO_L1_BITRATE_32K, 1293 | V4L2_MPEG_AUDIO_L1_BITRATE_64K, 1294 | V4L2_MPEG_AUDIO_L1_BITRATE_96K, 1295 | V4L2_MPEG_AUDIO_L1_BITRATE_128K, 1296 | V4L2_MPEG_AUDIO_L1_BITRATE_160K, 1297 | V4L2_MPEG_AUDIO_L1_BITRATE_192K, 1298 | V4L2_MPEG_AUDIO_L1_BITRATE_224K, 1299 | V4L2_MPEG_AUDIO_L1_BITRATE_256K, 1300 | V4L2_MPEG_AUDIO_L1_BITRATE_288K, 1301 | V4L2_MPEG_AUDIO_L1_BITRATE_320K, 1302 | V4L2_MPEG_AUDIO_L1_BITRATE_352K, 1303 | V4L2_MPEG_AUDIO_L1_BITRATE_384K, 1304 | V4L2_MPEG_AUDIO_L1_BITRATE_416K, 1305 | V4L2_MPEG_AUDIO_L1_BITRATE_448K, 1306 | ) = range(14) 1307 | 1308 | V4L2_CID_MPEG_AUDIO_L2_BITRATE = V4L2_CID_CODEC_BASE + 103 1309 | 1310 | v4l2_mpeg_audio_l2_bitrate = enum 1311 | ( 1312 | V4L2_MPEG_AUDIO_L2_BITRATE_32K, 1313 | V4L2_MPEG_AUDIO_L2_BITRATE_48K, 1314 | V4L2_MPEG_AUDIO_L2_BITRATE_56K, 1315 | V4L2_MPEG_AUDIO_L2_BITRATE_64K, 1316 | V4L2_MPEG_AUDIO_L2_BITRATE_80K, 1317 | V4L2_MPEG_AUDIO_L2_BITRATE_96K, 1318 | V4L2_MPEG_AUDIO_L2_BITRATE_112K, 1319 | V4L2_MPEG_AUDIO_L2_BITRATE_128K, 1320 | V4L2_MPEG_AUDIO_L2_BITRATE_160K, 1321 | V4L2_MPEG_AUDIO_L2_BITRATE_192K, 1322 | V4L2_MPEG_AUDIO_L2_BITRATE_224K, 1323 | V4L2_MPEG_AUDIO_L2_BITRATE_256K, 1324 | V4L2_MPEG_AUDIO_L2_BITRATE_320K, 1325 | V4L2_MPEG_AUDIO_L2_BITRATE_384K, 1326 | ) = range(14) 1327 | 1328 | V4L2_CID_MPEG_AUDIO_L3_BITRATE = V4L2_CID_CODEC_BASE + 104 1329 | 1330 | v4l2_mpeg_audio_l3_bitrate = enum 1331 | ( 1332 | V4L2_MPEG_AUDIO_L3_BITRATE_32K, 1333 | V4L2_MPEG_AUDIO_L3_BITRATE_40K, 1334 | V4L2_MPEG_AUDIO_L3_BITRATE_48K, 1335 | V4L2_MPEG_AUDIO_L3_BITRATE_56K, 1336 | V4L2_MPEG_AUDIO_L3_BITRATE_64K, 1337 | V4L2_MPEG_AUDIO_L3_BITRATE_80K, 1338 | V4L2_MPEG_AUDIO_L3_BITRATE_96K, 1339 | V4L2_MPEG_AUDIO_L3_BITRATE_112K, 1340 | V4L2_MPEG_AUDIO_L3_BITRATE_128K, 1341 | V4L2_MPEG_AUDIO_L3_BITRATE_160K, 1342 | V4L2_MPEG_AUDIO_L3_BITRATE_192K, 1343 | V4L2_MPEG_AUDIO_L3_BITRATE_224K, 1344 | V4L2_MPEG_AUDIO_L3_BITRATE_256K, 1345 | V4L2_MPEG_AUDIO_L3_BITRATE_320K, 1346 | ) = range(14) 1347 | 1348 | V4L2_CID_MPEG_AUDIO_MODE = V4L2_CID_CODEC_BASE + 105 1349 | 1350 | v4l2_mpeg_audio_mode = enum 1351 | ( 1352 | V4L2_MPEG_AUDIO_MODE_STEREO, 1353 | V4L2_MPEG_AUDIO_MODE_JOINT_STEREO, 1354 | V4L2_MPEG_AUDIO_MODE_DUAL, 1355 | V4L2_MPEG_AUDIO_MODE_MONO, 1356 | ) = range(4) 1357 | 1358 | V4L2_CID_MPEG_AUDIO_MODE_EXTENSION = V4L2_CID_CODEC_BASE + 106 1359 | 1360 | v4l2_mpeg_audio_mode_extension = enum 1361 | ( 1362 | V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_4, 1363 | V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_8, 1364 | V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_12, 1365 | V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_16, 1366 | ) = range(4) 1367 | 1368 | V4L2_CID_MPEG_AUDIO_EMPHASIS = V4L2_CID_CODEC_BASE + 107 1369 | 1370 | v4l2_mpeg_audio_emphasis = enum 1371 | ( 1372 | V4L2_MPEG_AUDIO_EMPHASIS_NONE, 1373 | V4L2_MPEG_AUDIO_EMPHASIS_50_DIV_15_uS, 1374 | V4L2_MPEG_AUDIO_EMPHASIS_CCITT_J17, 1375 | ) = range(3) 1376 | 1377 | V4L2_CID_MPEG_AUDIO_CRC = V4L2_CID_CODEC_BASE + 108 1378 | 1379 | v4l2_mpeg_audio_crc = enum 1380 | ( 1381 | V4L2_MPEG_AUDIO_CRC_NONE, 1382 | V4L2_MPEG_AUDIO_CRC_CRC16, 1383 | ) = range(2) 1384 | 1385 | V4L2_CID_MPEG_AUDIO_MUTE = V4L2_CID_CODEC_BASE + 109 1386 | V4L2_CID_MPEG_AUDIO_AAC_BITRATE = V4L2_CID_CODEC_BASE + 110 1387 | V4L2_CID_MPEG_AUDIO_AC3_BITRATE = V4L2_CID_CODEC_BASE + 111 1388 | 1389 | v4l2_mpeg_audio_ac3_bitrate = enum 1390 | ( 1391 | V4L2_MPEG_AUDIO_AC3_BITRATE_32K, 1392 | V4L2_MPEG_AUDIO_AC3_BITRATE_40K, 1393 | V4L2_MPEG_AUDIO_AC3_BITRATE_48K, 1394 | V4L2_MPEG_AUDIO_AC3_BITRATE_56K, 1395 | V4L2_MPEG_AUDIO_AC3_BITRATE_64K, 1396 | V4L2_MPEG_AUDIO_AC3_BITRATE_80K, 1397 | V4L2_MPEG_AUDIO_AC3_BITRATE_96K, 1398 | V4L2_MPEG_AUDIO_AC3_BITRATE_112K, 1399 | V4L2_MPEG_AUDIO_AC3_BITRATE_128K, 1400 | V4L2_MPEG_AUDIO_AC3_BITRATE_160K, 1401 | V4L2_MPEG_AUDIO_AC3_BITRATE_192K, 1402 | V4L2_MPEG_AUDIO_AC3_BITRATE_224K, 1403 | V4L2_MPEG_AUDIO_AC3_BITRATE_256K, 1404 | V4L2_MPEG_AUDIO_AC3_BITRATE_320K, 1405 | V4L2_MPEG_AUDIO_AC3_BITRATE_384K, 1406 | V4L2_MPEG_AUDIO_AC3_BITRATE_448K, 1407 | V4L2_MPEG_AUDIO_AC3_BITRATE_512K, 1408 | V4L2_MPEG_AUDIO_AC3_BITRATE_576K, 1409 | V4L2_MPEG_AUDIO_AC3_BITRATE_640K, 1410 | ) = range(19) 1411 | 1412 | V4L2_CID_MPEG_VIDEO_ENCODING = V4L2_CID_CODEC_BASE + 200 1413 | 1414 | v4l2_mpeg_video_encoding = enum 1415 | ( 1416 | V4L2_MPEG_VIDEO_ENCODING_MPEG_1, 1417 | V4L2_MPEG_VIDEO_ENCODING_MPEG_2, 1418 | V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC, 1419 | ) = range(3) 1420 | 1421 | V4L2_CID_MPEG_VIDEO_ASPECT = V4L2_CID_CODEC_BASE + 201 1422 | 1423 | v4l2_mpeg_video_aspect = enum 1424 | ( 1425 | V4L2_MPEG_VIDEO_ASPECT_1x1, 1426 | V4L2_MPEG_VIDEO_ASPECT_4x3, 1427 | V4L2_MPEG_VIDEO_ASPECT_16x9, 1428 | V4L2_MPEG_VIDEO_ASPECT_221x100, 1429 | ) = range(4) 1430 | 1431 | V4L2_CID_MPEG_VIDEO_B_FRAMES = V4L2_CID_CODEC_BASE + 202 1432 | V4L2_CID_MPEG_VIDEO_GOP_SIZE = V4L2_CID_CODEC_BASE + 203 1433 | V4L2_CID_MPEG_VIDEO_GOP_CLOSURE = V4L2_CID_CODEC_BASE + 204 1434 | V4L2_CID_MPEG_VIDEO_PULLDOWN = V4L2_CID_CODEC_BASE + 205 1435 | V4L2_CID_MPEG_VIDEO_BITRATE_MODE = V4L2_CID_CODEC_BASE + 206 1436 | 1437 | v4l2_mpeg_video_bitrate_mode = enum 1438 | ( 1439 | V4L2_MPEG_VIDEO_BITRATE_MODE_VBR, 1440 | V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 1441 | ) = range(2) 1442 | 1443 | V4L2_CID_MPEG_VIDEO_BITRATE = V4L2_CID_CODEC_BASE + 207 1444 | V4L2_CID_MPEG_VIDEO_BITRATE_PEAK = V4L2_CID_CODEC_BASE + 208 1445 | V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION = V4L2_CID_CODEC_BASE + 209 1446 | V4L2_CID_MPEG_VIDEO_MUTE = V4L2_CID_CODEC_BASE + 210 1447 | V4L2_CID_MPEG_VIDEO_MUTE_YUV = V4L2_CID_CODEC_BASE + 211 1448 | V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME = V4L2_CID_CODEC_BASE + 229 1449 | 1450 | 1451 | V4L2_CID_MPEG_CX2341X_BASE = V4L2_CID_CODEC_BASE | 0x1000 1452 | V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE = V4L2_CID_MPEG_CX2341X_BASE + 0 1453 | 1454 | v4l2_mpeg_cx2341x_video_spatial_filter_mode = enum 1455 | ( 1456 | V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_MANUAL, 1457 | V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_AUTO, 1458 | ) = range(2) 1459 | 1460 | V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER = V4L2_CID_MPEG_CX2341X_BASE + 1 1461 | V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 2 1462 | 1463 | v4l2_mpeg_cx2341x_video_luma_spatial_filter_type = enum 1464 | ( 1465 | V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_OFF, 1466 | V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_HOR, 1467 | V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_VERT, 1468 | V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_HV_SEPARABLE, 1469 | V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_SYM_NON_SEPARABLE, 1470 | ) = range(5) 1471 | 1472 | V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 3 1473 | 1474 | v4l2_mpeg_cx2341x_video_chroma_spatial_filter_type = enum 1475 | ( 1476 | V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_OFF, 1477 | V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_1D_HOR, 1478 | ) = range(2) 1479 | 1480 | V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE = V4L2_CID_MPEG_CX2341X_BASE + 4 1481 | 1482 | v4l2_mpeg_cx2341x_video_temporal_filter_mode = enum 1483 | ( 1484 | V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_MANUAL, 1485 | V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_AUTO, 1486 | ) = range(2) 1487 | 1488 | V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER = V4L2_CID_MPEG_CX2341X_BASE + 5 1489 | V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 6 1490 | 1491 | v4l2_mpeg_cx2341x_video_median_filter_type = enum 1492 | ( 1493 | V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_OFF, 1494 | V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR, 1495 | V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_VERT, 1496 | V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR_VERT, 1497 | V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_DIAG, 1498 | ) = range(5) 1499 | 1500 | V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM = V4L2_CID_MPEG_CX2341X_BASE + 7 1501 | V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP = V4L2_CID_MPEG_CX2341X_BASE + 8 1502 | V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM = V4L2_CID_MPEG_CX2341X_BASE + 9 1503 | V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP = V4L2_CID_MPEG_CX2341X_BASE + 10 1504 | V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS = V4L2_CID_MPEG_CX2341X_BASE + 11 1505 | 1506 | V4L2_CID_CAMERA_CLASS_BASE = V4L2_CTRL_CLASS_CAMERA | 0x900 1507 | V4L2_CID_CAMERA_CLASS = V4L2_CTRL_CLASS_CAMERA | 1 1508 | 1509 | V4L2_CID_EXPOSURE_AUTO = V4L2_CID_CAMERA_CLASS_BASE + 1 1510 | 1511 | v4l2_exposure_auto_type = enum 1512 | ( 1513 | V4L2_EXPOSURE_AUTO, 1514 | V4L2_EXPOSURE_MANUAL, 1515 | V4L2_EXPOSURE_SHUTTER_PRIORITY, 1516 | V4L2_EXPOSURE_APERTURE_PRIORITY, 1517 | ) = range(4) 1518 | 1519 | V4L2_CID_EXPOSURE_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 2 1520 | V4L2_CID_EXPOSURE_AUTO_PRIORITY = V4L2_CID_CAMERA_CLASS_BASE + 3 1521 | 1522 | V4L2_CID_PAN_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 4 1523 | V4L2_CID_TILT_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 5 1524 | V4L2_CID_PAN_RESET = V4L2_CID_CAMERA_CLASS_BASE + 6 1525 | V4L2_CID_TILT_RESET = V4L2_CID_CAMERA_CLASS_BASE + 7 1526 | 1527 | V4L2_CID_PAN_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 8 1528 | V4L2_CID_TILT_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 9 1529 | 1530 | V4L2_CID_FOCUS_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 10 1531 | V4L2_CID_FOCUS_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 11 1532 | V4L2_CID_FOCUS_AUTO = V4L2_CID_CAMERA_CLASS_BASE + 12 1533 | 1534 | V4L2_CID_ZOOM_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 13 1535 | V4L2_CID_ZOOM_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 14 1536 | V4L2_CID_ZOOM_CONTINUOUS = V4L2_CID_CAMERA_CLASS_BASE + 15 1537 | 1538 | V4L2_CID_PRIVACY = V4L2_CID_CAMERA_CLASS_BASE + 16 1539 | 1540 | V4L2_CID_IRIS_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 17 1541 | V4L2_CID_IRIS_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 18 1542 | 1543 | V4L2_CID_AUTO_EXPOSURE_BIAS = V4L2_CID_CAMERA_CLASS_BASE + 19 1544 | 1545 | V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE = V4L2_CID_CAMERA_CLASS_BASE + 20 1546 | v4l2_auto_n_preset_white_balance = enum 1547 | ( 1548 | V4L2_WHITE_BALANCE_MANUAL, 1549 | V4L2_WHITE_BALANCE_AUTO, 1550 | V4L2_WHITE_BALANCE_INCANDESCENT, 1551 | V4L2_WHITE_BALANCE_FLUORESCENT, 1552 | V4L2_WHITE_BALANCE_FLUORESCENT_H, 1553 | V4L2_WHITE_BALANCE_HORIZON, 1554 | V4L2_WHITE_BALANCE_DAYLIGHT, 1555 | V4L2_WHITE_BALANCE_FLASH, 1556 | V4L2_WHITE_BALANCE_CLOUDY, 1557 | V4L2_WHITE_BALANCE_SHADE, 1558 | ) = range(10) 1559 | 1560 | V4L2_CID_WIDE_DYNAMIC_RANGE = V4L2_CID_CAMERA_CLASS_BASE + 21 1561 | V4L2_CID_IMAGE_STABILIZATION = V4L2_CID_CAMERA_CLASS_BASE + 22 1562 | 1563 | V4L2_CID_ISO_SENSITIVITY = V4L2_CID_CAMERA_CLASS_BASE + 23 1564 | V4L2_CID_ISO_SENSITIVITY_AUTO = V4L2_CID_CAMERA_CLASS_BASE + 24 1565 | v4l2_iso_sensitivity_auto_type = enum 1566 | ( 1567 | V4L2_ISO_SENSITIVITY_MANUAL, 1568 | V4L2_ISO_SENSITIVITY_AUTO, 1569 | ) = range(2) 1570 | 1571 | V4L2_CID_EXPOSURE_METERING = V4L2_CID_CAMERA_CLASS_BASE + 25 1572 | v4l2_exposure_metering = enum 1573 | ( 1574 | V4L2_EXPOSURE_METERING_AVERAGE, 1575 | V4L2_EXPOSURE_METERING_CENTER_WEIGHTED, 1576 | V4L2_EXPOSURE_METERING_SPOT, 1577 | V4L2_EXPOSURE_METERING_MATRIX, 1578 | ) = range(4) 1579 | 1580 | V4L2_CID_SCENE_MODE = V4L2_CID_CAMERA_CLASS_BASE + 26 1581 | v4l2_scene_mode = enum 1582 | ( 1583 | V4L2_SCENE_MODE_NONE, 1584 | V4L2_SCENE_MODE_BACKLIGHT, 1585 | V4L2_SCENE_MODE_BEACH_SNOW, 1586 | V4L2_SCENE_MODE_CANDLE_LIGHT, 1587 | V4L2_SCENE_MODE_DAWN_DUSK, 1588 | V4L2_SCENE_MODE_FALL_COLORS, 1589 | V4L2_SCENE_MODE_FIREWORKS, 1590 | V4L2_SCENE_MODE_LANDSCAPE, 1591 | V4L2_SCENE_MODE_NIGHT, 1592 | V4L2_SCENE_MODE_PARTY_INDOOR, 1593 | V4L2_SCENE_MODE_PORTRAIT, 1594 | V4L2_SCENE_MODE_SPORTS, 1595 | V4L2_SCENE_MODE_SUNSET, 1596 | V4L2_SCENE_MODE_TEXT, 1597 | ) = range(14) 1598 | 1599 | V4L2_CID_3A_LOCK = V4L2_CID_CAMERA_CLASS_BASE + 27 1600 | V4L2_LOCK_EXPOSURE = (1 << 0) 1601 | V4L2_LOCK_WHITE_BALANCE = (1 << 1) 1602 | V4L2_LOCK_FOCUS = (1 << 2) 1603 | 1604 | V4L2_CID_AUTO_FOCUS_START = V4L2_CID_CAMERA_CLASS_BASE + 28 1605 | V4L2_CID_AUTO_FOCUS_STOP = V4L2_CID_CAMERA_CLASS_BASE + 29 1606 | V4L2_CID_AUTO_FOCUS_STATUS = V4L2_CID_CAMERA_CLASS_BASE + 30 1607 | V4L2_AUTO_FOCUS_STATUS_IDLE = 0 << 0 1608 | V4L2_AUTO_FOCUS_STATUS_BUSY = 1 << 0 1609 | V4L2_AUTO_FOCUS_STATUS_REACHED = 1 << 1 1610 | V4L2_AUTO_FOCUS_STATUS_FAILED = 1 << 2 1611 | 1612 | V4L2_CID_AUTO_FOCUS_RANGE = V4L2_CID_CAMERA_CLASS_BASE + 31 1613 | v4l2_auto_focus_range = enum 1614 | ( 1615 | V4L2_AUTO_FOCUS_RANGE_AUTO, 1616 | V4L2_AUTO_FOCUS_RANGE_NORMAL, 1617 | V4L2_AUTO_FOCUS_RANGE_MACRO, 1618 | V4L2_AUTO_FOCUS_RANGE_INFINITY, 1619 | ) = range(4) 1620 | 1621 | V4L2_CID_PAN_SPEED = V4L2_CID_CAMERA_CLASS_BASE + 32 1622 | V4L2_CID_TILT_SPEED = V4L2_CID_CAMERA_CLASS_BASE + 33 1623 | 1624 | V4L2_CID_CAMERA_ORIENTATION = V4L2_CID_CAMERA_CLASS_BASE + 34 1625 | V4L2_CAMERA_ORIENTATION_FRONT = 0 1626 | V4L2_CAMERA_ORIENTATION_BACK = 1 1627 | V4L2_CAMERA_ORIENTATION_EXTERNAL = 2 1628 | 1629 | V4L2_CID_CAMERA_SENSOR_ROTATION = V4L2_CID_CAMERA_CLASS_BASE + 35 1630 | 1631 | # FM Modulator class control IDs 1632 | 1633 | V4L2_CID_FM_TX_CLASS_BASE = V4L2_CTRL_CLASS_FM_TX | 0x900 1634 | V4L2_CID_FM_TX_CLASS = V4L2_CTRL_CLASS_FM_TX | 1 1635 | 1636 | V4L2_CID_RDS_TX_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 1 1637 | V4L2_CID_RDS_TX_PI = V4L2_CID_FM_TX_CLASS_BASE + 2 1638 | V4L2_CID_RDS_TX_PTY = V4L2_CID_FM_TX_CLASS_BASE + 3 1639 | V4L2_CID_RDS_TX_PS_NAME = V4L2_CID_FM_TX_CLASS_BASE + 5 1640 | V4L2_CID_RDS_TX_RADIO_TEXT = V4L2_CID_FM_TX_CLASS_BASE + 6 1641 | 1642 | V4L2_CID_AUDIO_LIMITER_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 64 1643 | V4L2_CID_AUDIO_LIMITER_RELEASE_TIME = V4L2_CID_FM_TX_CLASS_BASE + 65 1644 | V4L2_CID_AUDIO_LIMITER_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 66 1645 | 1646 | V4L2_CID_AUDIO_COMPRESSION_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 80 1647 | V4L2_CID_AUDIO_COMPRESSION_GAIN = V4L2_CID_FM_TX_CLASS_BASE + 81 1648 | V4L2_CID_AUDIO_COMPRESSION_THRESHOLD = V4L2_CID_FM_TX_CLASS_BASE + 82 1649 | V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME = V4L2_CID_FM_TX_CLASS_BASE + 83 1650 | V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME = V4L2_CID_FM_TX_CLASS_BASE + 84 1651 | 1652 | V4L2_CID_PILOT_TONE_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 96 1653 | V4L2_CID_PILOT_TONE_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 97 1654 | V4L2_CID_PILOT_TONE_FREQUENCY = V4L2_CID_FM_TX_CLASS_BASE + 98 1655 | 1656 | V4L2_CID_TUNE_PREEMPHASIS = V4L2_CID_FM_TX_CLASS_BASE + 112 1657 | 1658 | v4l2_preemphasis = enum 1659 | ( 1660 | V4L2_PREEMPHASIS_DISABLED, 1661 | V4L2_PREEMPHASIS_50_uS, 1662 | V4L2_PREEMPHASIS_75_uS, 1663 | ) = range(3) 1664 | 1665 | V4L2_CID_TUNE_POWER_LEVEL = V4L2_CID_FM_TX_CLASS_BASE + 113 1666 | V4L2_CID_TUNE_ANTENNA_CAPACITOR = V4L2_CID_FM_TX_CLASS_BASE + 114 1667 | 1668 | 1669 | # 1670 | # Tuning 1671 | # 1672 | 1673 | class v4l2_tuner(ctypes.Structure): 1674 | _fields_ = [ 1675 | ('index', ctypes.c_uint32), 1676 | ('name', ctypes.c_char * 32), 1677 | ('type', v4l2_tuner_type), 1678 | ('capability', ctypes.c_uint32), 1679 | ('rangelow', ctypes.c_uint32), 1680 | ('rangehigh', ctypes.c_uint32), 1681 | ('rxsubchans', ctypes.c_uint32), 1682 | ('audmode', ctypes.c_uint32), 1683 | ('signal', ctypes.c_int32), 1684 | ('afc', ctypes.c_int32), 1685 | ('reserved', ctypes.c_uint32 * 4), 1686 | ] 1687 | 1688 | 1689 | class v4l2_modulator(ctypes.Structure): 1690 | _fields_ = [ 1691 | ('index', ctypes.c_uint32), 1692 | ('name', ctypes.c_char * 32), 1693 | ('capability', ctypes.c_uint32), 1694 | ('rangelow', ctypes.c_uint32), 1695 | ('rangehigh', ctypes.c_uint32), 1696 | ('txsubchans', ctypes.c_uint32), 1697 | ('reserved', ctypes.c_uint32 * 4), 1698 | ] 1699 | 1700 | 1701 | V4L2_TUNER_CAP_LOW = 0x0001 1702 | V4L2_TUNER_CAP_NORM = 0x0002 1703 | V4L2_TUNER_CAP_STEREO = 0x0010 1704 | V4L2_TUNER_CAP_LANG2 = 0x0020 1705 | V4L2_TUNER_CAP_SAP = 0x0020 1706 | V4L2_TUNER_CAP_LANG1 = 0x0040 1707 | V4L2_TUNER_CAP_RDS = 0x0080 1708 | 1709 | V4L2_TUNER_SUB_MONO = 0x0001 1710 | V4L2_TUNER_SUB_STEREO = 0x0002 1711 | V4L2_TUNER_SUB_LANG2 = 0x0004 1712 | V4L2_TUNER_SUB_SAP = 0x0004 1713 | V4L2_TUNER_SUB_LANG1 = 0x0008 1714 | V4L2_TUNER_SUB_RDS = 0x0010 1715 | 1716 | V4L2_TUNER_MODE_MONO = 0x0000 1717 | V4L2_TUNER_MODE_STEREO = 0x0001 1718 | V4L2_TUNER_MODE_LANG2 = 0x0002 1719 | V4L2_TUNER_MODE_SAP = 0x0002 1720 | V4L2_TUNER_MODE_LANG1 = 0x0003 1721 | V4L2_TUNER_MODE_LANG1_LANG2 = 0x0004 1722 | 1723 | 1724 | class v4l2_frequency(ctypes.Structure): 1725 | _fields_ = [ 1726 | ('tuner', ctypes.c_uint32), 1727 | ('type', v4l2_tuner_type), 1728 | ('frequency', ctypes.c_uint32), 1729 | ('reserved', ctypes.c_uint32 * 8), 1730 | ] 1731 | 1732 | 1733 | class v4l2_hw_freq_seek(ctypes.Structure): 1734 | _fields_ = [ 1735 | ('tuner', ctypes.c_uint32), 1736 | ('type', v4l2_tuner_type), 1737 | ('seek_upward', ctypes.c_uint32), 1738 | ('wrap_around', ctypes.c_uint32), 1739 | ('reserved', ctypes.c_uint32 * 8), 1740 | ] 1741 | 1742 | 1743 | # 1744 | # RDS 1745 | # 1746 | 1747 | class v4l2_rds_data(ctypes.Structure): 1748 | _fields_ = [ 1749 | ('lsb', ctypes.c_char), 1750 | ('msb', ctypes.c_char), 1751 | ('block', ctypes.c_char), 1752 | ] 1753 | 1754 | _pack_ = True 1755 | 1756 | 1757 | V4L2_RDS_BLOCK_MSK = 0x7 1758 | V4L2_RDS_BLOCK_A = 0 1759 | V4L2_RDS_BLOCK_B = 1 1760 | V4L2_RDS_BLOCK_C = 2 1761 | V4L2_RDS_BLOCK_D = 3 1762 | V4L2_RDS_BLOCK_C_ALT = 4 1763 | V4L2_RDS_BLOCK_INVALID = 7 1764 | 1765 | V4L2_RDS_BLOCK_CORRECTED = 0x40 1766 | V4L2_RDS_BLOCK_ERROR = 0x80 1767 | 1768 | 1769 | # 1770 | # Audio 1771 | # 1772 | 1773 | class v4l2_audio(ctypes.Structure): 1774 | _fields_ = [ 1775 | ('index', ctypes.c_uint32), 1776 | ('name', ctypes.c_char * 32), 1777 | ('capability', ctypes.c_uint32), 1778 | ('mode', ctypes.c_uint32), 1779 | ('reserved', ctypes.c_uint32 * 2), 1780 | ] 1781 | 1782 | 1783 | V4L2_AUDCAP_STEREO = 0x00001 1784 | V4L2_AUDCAP_AVL = 0x00002 1785 | 1786 | V4L2_AUDMODE_AVL = 0x00001 1787 | 1788 | 1789 | class v4l2_audioout(ctypes.Structure): 1790 | _fields_ = [ 1791 | ('index', ctypes.c_uint32), 1792 | ('name', ctypes.c_char * 32), 1793 | ('capability', ctypes.c_uint32), 1794 | ('mode', ctypes.c_uint32), 1795 | ('reserved', ctypes.c_uint32 * 2), 1796 | ] 1797 | 1798 | 1799 | # 1800 | # Mpeg services (experimental) 1801 | # 1802 | 1803 | V4L2_ENC_IDX_FRAME_I = 0 1804 | V4L2_ENC_IDX_FRAME_P = 1 1805 | V4L2_ENC_IDX_FRAME_B = 2 1806 | V4L2_ENC_IDX_FRAME_MASK = 0xf 1807 | 1808 | 1809 | class v4l2_enc_idx_entry(ctypes.Structure): 1810 | _fields_ = [ 1811 | ('offset', ctypes.c_uint64), 1812 | ('pts', ctypes.c_uint64), 1813 | ('length', ctypes.c_uint32), 1814 | ('flags', ctypes.c_uint32), 1815 | ('reserved', ctypes.c_uint32 * 2), 1816 | ] 1817 | 1818 | 1819 | V4L2_ENC_IDX_ENTRIES = 64 1820 | 1821 | 1822 | class v4l2_enc_idx(ctypes.Structure): 1823 | _fields_ = [ 1824 | ('entries', ctypes.c_uint32), 1825 | ('entries_cap', ctypes.c_uint32), 1826 | ('reserved', ctypes.c_uint32 * 4), 1827 | ('entry', v4l2_enc_idx_entry * V4L2_ENC_IDX_ENTRIES), 1828 | ] 1829 | 1830 | 1831 | V4L2_ENC_CMD_START = 0 1832 | V4L2_ENC_CMD_STOP = 1 1833 | V4L2_ENC_CMD_PAUSE = 2 1834 | V4L2_ENC_CMD_RESUME = 3 1835 | 1836 | V4L2_ENC_CMD_STOP_AT_GOP_END = 1 << 0 1837 | 1838 | 1839 | class v4l2_encoder_cmd(ctypes.Structure): 1840 | class _u(ctypes.Union): 1841 | class _s(ctypes.Structure): 1842 | _fields_ = [ 1843 | ('data', ctypes.c_uint32 * 8), 1844 | ] 1845 | 1846 | _fields_ = [ 1847 | ('raw', _s), 1848 | ] 1849 | 1850 | _fields_ = [ 1851 | ('cmd', ctypes.c_uint32), 1852 | ('flags', ctypes.c_uint32), 1853 | ('_u', _u), 1854 | ] 1855 | 1856 | _anonymous_ = ('_u',) 1857 | 1858 | 1859 | # 1860 | # Data services (VBI) 1861 | # 1862 | 1863 | class v4l2_vbi_format(ctypes.Structure): 1864 | _fields_ = [ 1865 | ('sampling_rate', ctypes.c_uint32), 1866 | ('offset', ctypes.c_uint32), 1867 | ('samples_per_line', ctypes.c_uint32), 1868 | ('sample_format', ctypes.c_uint32), 1869 | ('start', ctypes.c_int32 * 2), 1870 | ('count', ctypes.c_uint32 * 2), 1871 | ('flags', ctypes.c_uint32), 1872 | ('reserved', ctypes.c_uint32 * 2), 1873 | ] 1874 | 1875 | 1876 | V4L2_VBI_UNSYNC = 1 << 0 1877 | V4L2_VBI_INTERLACED = 1 << 1 1878 | 1879 | 1880 | class v4l2_sliced_vbi_format(ctypes.Structure): 1881 | _fields_ = [ 1882 | ('service_set', ctypes.c_uint16), 1883 | ('service_lines', ctypes.c_uint16 * 2 * 24), 1884 | ('io_size', ctypes.c_uint32), 1885 | ('reserved', ctypes.c_uint32 * 2), 1886 | ] 1887 | 1888 | 1889 | V4L2_SLICED_TELETEXT_B = 0x0001 1890 | V4L2_SLICED_VPS = 0x0400 1891 | V4L2_SLICED_CAPTION_525 = 0x1000 1892 | V4L2_SLICED_WSS_625 = 0x4000 1893 | V4L2_SLICED_VBI_525 = V4L2_SLICED_CAPTION_525 1894 | V4L2_SLICED_VBI_625 = ( 1895 | V4L2_SLICED_TELETEXT_B | V4L2_SLICED_VPS | V4L2_SLICED_WSS_625) 1896 | 1897 | 1898 | class v4l2_sliced_vbi_cap(ctypes.Structure): 1899 | _fields_ = [ 1900 | ('service_set', ctypes.c_uint16), 1901 | ('service_lines', ctypes.c_uint16 * 2 * 24), 1902 | ('type', v4l2_buf_type), 1903 | ('reserved', ctypes.c_uint32 * 3), 1904 | ] 1905 | 1906 | 1907 | class v4l2_sliced_vbi_data(ctypes.Structure): 1908 | _fields_ = [ 1909 | ('id', ctypes.c_uint32), 1910 | ('field', ctypes.c_uint32), 1911 | ('line', ctypes.c_uint32), 1912 | ('reserved', ctypes.c_uint32), 1913 | ('data', ctypes.c_char * 48), 1914 | ] 1915 | 1916 | 1917 | # 1918 | # Sliced VBI data inserted into MPEG Streams 1919 | # 1920 | 1921 | 1922 | V4L2_MPEG_VBI_IVTV_TELETEXT_B = 1 1923 | V4L2_MPEG_VBI_IVTV_CAPTION_525 = 4 1924 | V4L2_MPEG_VBI_IVTV_WSS_625 = 5 1925 | V4L2_MPEG_VBI_IVTV_VPS = 7 1926 | 1927 | 1928 | class v4l2_mpeg_vbi_itv0_line(ctypes.Structure): 1929 | _fields_ = [ 1930 | ('id', ctypes.c_char), 1931 | ('data', ctypes.c_char * 42), 1932 | ] 1933 | 1934 | _pack_ = True 1935 | 1936 | 1937 | class v4l2_mpeg_vbi_itv0(ctypes.Structure): 1938 | _fields_ = [ 1939 | ('linemask', ctypes.c_uint32 * 2), # how to define __le32 in ctypes? 1940 | ('line', v4l2_mpeg_vbi_itv0_line * 35), 1941 | ] 1942 | 1943 | _pack_ = True 1944 | 1945 | 1946 | class v4l2_mpeg_vbi_ITV0(ctypes.Structure): 1947 | _fields_ = [ 1948 | ('line', v4l2_mpeg_vbi_itv0_line * 36), 1949 | ] 1950 | 1951 | _pack_ = True 1952 | 1953 | 1954 | V4L2_MPEG_VBI_IVTV_MAGIC0 = "itv0" 1955 | V4L2_MPEG_VBI_IVTV_MAGIC1 = "ITV0" 1956 | 1957 | 1958 | class v4l2_mpeg_vbi_fmt_ivtv(ctypes.Structure): 1959 | class _u(ctypes.Union): 1960 | _fields_ = [ 1961 | ('itv0', v4l2_mpeg_vbi_itv0), 1962 | ('ITV0', v4l2_mpeg_vbi_ITV0), 1963 | ] 1964 | 1965 | _fields_ = [ 1966 | ('magic', ctypes.c_char * 4), 1967 | ('_u', _u) 1968 | ] 1969 | 1970 | _anonymous_ = ('_u',) 1971 | _pack_ = True 1972 | 1973 | 1974 | # 1975 | # Aggregate structures 1976 | # 1977 | 1978 | class v4l2_format(ctypes.Structure): 1979 | class _u(ctypes.Union): 1980 | _fields_ = [ 1981 | ('pix', v4l2_pix_format), 1982 | ('pix_mp', v4l2_pix_format_mplane), 1983 | ('win', v4l2_window), 1984 | ('vbi', v4l2_vbi_format), 1985 | ('sliced', v4l2_sliced_vbi_format), 1986 | ('raw_data', ctypes.c_char * 200), 1987 | ] 1988 | 1989 | _fields_ = [ 1990 | ('type', v4l2_buf_type), 1991 | ('fmt', _u), 1992 | ] 1993 | 1994 | 1995 | class v4l2_streamparm(ctypes.Structure): 1996 | class _u(ctypes.Union): 1997 | _fields_ = [ 1998 | ('capture', v4l2_captureparm), 1999 | ('output', v4l2_outputparm), 2000 | ('raw_data', ctypes.c_char * 200), 2001 | ] 2002 | 2003 | _fields_ = [ 2004 | ('type', v4l2_buf_type), 2005 | ('parm', _u) 2006 | ] 2007 | 2008 | 2009 | # 2010 | # Advanced debugging 2011 | # 2012 | 2013 | V4L2_CHIP_MATCH_HOST = 0 2014 | V4L2_CHIP_MATCH_I2C_DRIVER = 1 2015 | V4L2_CHIP_MATCH_I2C_ADDR = 2 2016 | V4L2_CHIP_MATCH_AC97 = 3 2017 | 2018 | 2019 | class v4l2_dbg_match(ctypes.Structure): 2020 | class _u(ctypes.Union): 2021 | _fields_ = [ 2022 | ('addr', ctypes.c_uint32), 2023 | ('name', ctypes.c_char * 32), 2024 | ] 2025 | 2026 | _fields_ = [ 2027 | ('type', ctypes.c_uint32), 2028 | ('_u', _u), 2029 | ] 2030 | 2031 | _anonymous_ = ('_u',) 2032 | _pack_ = True 2033 | 2034 | 2035 | class v4l2_dbg_register(ctypes.Structure): 2036 | _fields_ = [ 2037 | ('match', v4l2_dbg_match), 2038 | ('size', ctypes.c_uint32), 2039 | ('reg', ctypes.c_uint64), 2040 | ('val', ctypes.c_uint64), 2041 | ] 2042 | 2043 | _pack_ = True 2044 | 2045 | 2046 | class v4l2_dbg_chip_ident(ctypes.Structure): 2047 | _fields_ = [ 2048 | ('match', v4l2_dbg_match), 2049 | ('ident', ctypes.c_uint32), 2050 | ('revision', ctypes.c_uint32), 2051 | ] 2052 | 2053 | _pack_ = True 2054 | 2055 | 2056 | # 2057 | # ioctl codes for video devices 2058 | # 2059 | 2060 | VIDIOC_QUERYCAP = _IOR('V', 0, v4l2_capability) 2061 | VIDIOC_RESERVED = _IO('V', 1) 2062 | VIDIOC_ENUM_FMT = _IOWR('V', 2, v4l2_fmtdesc) 2063 | VIDIOC_G_FMT = _IOWR('V', 4, v4l2_format) 2064 | VIDIOC_S_FMT = _IOWR('V', 5, v4l2_format) 2065 | VIDIOC_REQBUFS = _IOWR('V', 8, v4l2_requestbuffers) 2066 | VIDIOC_QUERYBUF = _IOWR('V', 9, v4l2_buffer) 2067 | VIDIOC_G_FBUF = _IOR('V', 10, v4l2_framebuffer) 2068 | VIDIOC_S_FBUF = _IOW('V', 11, v4l2_framebuffer) 2069 | VIDIOC_OVERLAY = _IOW('V', 14, ctypes.c_int) 2070 | VIDIOC_QBUF = _IOWR('V', 15, v4l2_buffer) 2071 | VIDIOC_EXPBUF = _IOWR('V', 16, v4l2_exportbuffer) 2072 | VIDIOC_DQBUF = _IOWR('V', 17, v4l2_buffer) 2073 | VIDIOC_STREAMON = _IOW('V', 18, ctypes.c_int) 2074 | VIDIOC_STREAMOFF = _IOW('V', 19, ctypes.c_int) 2075 | VIDIOC_G_PARM = _IOWR('V', 21, v4l2_streamparm) 2076 | VIDIOC_S_PARM = _IOWR('V', 22, v4l2_streamparm) 2077 | VIDIOC_G_STD = _IOR('V', 23, v4l2_std_id) 2078 | VIDIOC_S_STD = _IOW('V', 24, v4l2_std_id) 2079 | VIDIOC_ENUMSTD = _IOWR('V', 25, v4l2_standard) 2080 | VIDIOC_ENUMINPUT = _IOWR('V', 26, v4l2_input) 2081 | VIDIOC_G_CTRL = _IOWR('V', 27, v4l2_control) 2082 | VIDIOC_S_CTRL = _IOWR('V', 28, v4l2_control) 2083 | VIDIOC_G_TUNER = _IOWR('V', 29, v4l2_tuner) 2084 | VIDIOC_S_TUNER = _IOW('V', 30, v4l2_tuner) 2085 | VIDIOC_G_AUDIO = _IOR('V', 33, v4l2_audio) 2086 | VIDIOC_S_AUDIO = _IOW('V', 34, v4l2_audio) 2087 | VIDIOC_QUERYCTRL = _IOWR('V', 36, v4l2_queryctrl) 2088 | VIDIOC_QUERYMENU = _IOWR('V', 37, v4l2_querymenu) 2089 | VIDIOC_G_INPUT = _IOR('V', 38, ctypes.c_int) 2090 | VIDIOC_S_INPUT = _IOWR('V', 39, ctypes.c_int) 2091 | VIDIOC_G_OUTPUT = _IOR('V', 46, ctypes.c_int) 2092 | VIDIOC_S_OUTPUT = _IOWR('V', 47, ctypes.c_int) 2093 | VIDIOC_ENUMOUTPUT = _IOWR('V', 48, v4l2_output) 2094 | VIDIOC_G_AUDOUT = _IOR('V', 49, v4l2_audioout) 2095 | VIDIOC_S_AUDOUT = _IOW('V', 50, v4l2_audioout) 2096 | VIDIOC_G_MODULATOR = _IOWR('V', 54, v4l2_modulator) 2097 | VIDIOC_S_MODULATOR = _IOW('V', 55, v4l2_modulator) 2098 | VIDIOC_G_FREQUENCY = _IOWR('V', 56, v4l2_frequency) 2099 | VIDIOC_S_FREQUENCY = _IOW('V', 57, v4l2_frequency) 2100 | VIDIOC_CROPCAP = _IOWR('V', 58, v4l2_cropcap) 2101 | VIDIOC_G_CROP = _IOWR('V', 59, v4l2_crop) 2102 | VIDIOC_S_CROP = _IOW('V', 60, v4l2_crop) 2103 | VIDIOC_G_JPEGCOMP = _IOR('V', 61, v4l2_jpegcompression) 2104 | VIDIOC_S_JPEGCOMP = _IOW('V', 62, v4l2_jpegcompression) 2105 | VIDIOC_QUERYSTD = _IOR('V', 63, v4l2_std_id) 2106 | VIDIOC_TRY_FMT = _IOWR('V', 64, v4l2_format) 2107 | VIDIOC_ENUMAUDIO = _IOWR('V', 65, v4l2_audio) 2108 | VIDIOC_ENUMAUDOUT = _IOWR('V', 66, v4l2_audioout) 2109 | VIDIOC_G_PRIORITY = _IOR('V', 67, v4l2_priority) 2110 | VIDIOC_S_PRIORITY = _IOW('V', 68, v4l2_priority) 2111 | VIDIOC_G_SLICED_VBI_CAP = _IOWR('V', 69, v4l2_sliced_vbi_cap) 2112 | VIDIOC_LOG_STATUS = _IO('V', 70) 2113 | VIDIOC_G_EXT_CTRLS = _IOWR('V', 71, v4l2_ext_controls) 2114 | VIDIOC_S_EXT_CTRLS = _IOWR('V', 72, v4l2_ext_controls) 2115 | VIDIOC_TRY_EXT_CTRLS = _IOWR('V', 73, v4l2_ext_controls) 2116 | 2117 | VIDIOC_ENUM_FRAMESIZES = _IOWR('V', 74, v4l2_frmsizeenum) 2118 | VIDIOC_ENUM_FRAMEINTERVALS = _IOWR('V', 75, v4l2_frmivalenum) 2119 | VIDIOC_G_ENC_INDEX = _IOR('V', 76, v4l2_enc_idx) 2120 | VIDIOC_ENCODER_CMD = _IOWR('V', 77, v4l2_encoder_cmd) 2121 | VIDIOC_TRY_ENCODER_CMD = _IOWR('V', 78, v4l2_encoder_cmd) 2122 | 2123 | VIDIOC_DBG_S_REGISTER = _IOW('V', 79, v4l2_dbg_register) 2124 | VIDIOC_DBG_G_REGISTER = _IOWR('V', 80, v4l2_dbg_register) 2125 | 2126 | VIDIOC_DBG_G_CHIP_IDENT = _IOWR('V', 81, v4l2_dbg_chip_ident) 2127 | 2128 | VIDIOC_S_HW_FREQ_SEEK = _IOW('V', 82, v4l2_hw_freq_seek) 2129 | VIDIOC_ENUM_DV_PRESETS = _IOWR('V', 83, v4l2_dv_enum_preset) 2130 | VIDIOC_S_DV_PRESET = _IOWR('V', 84, v4l2_dv_preset) 2131 | VIDIOC_G_DV_PRESET = _IOWR('V', 85, v4l2_dv_preset) 2132 | VIDIOC_QUERY_DV_PRESET = _IOR('V', 86, v4l2_dv_preset) 2133 | VIDIOC_S_DV_TIMINGS = _IOWR('V', 87, v4l2_dv_timings) 2134 | VIDIOC_G_DV_TIMINGS = _IOWR('V', 88, v4l2_dv_timings) 2135 | VIDIOC_G_SELECTION = _IOWR('V', 94, v4l2_selection) 2136 | VIDIOC_S_SELECTION =_IOWR('V', 95, v4l2_selection) 2137 | 2138 | VIDIOC_OVERLAY_OLD = _IOWR('V', 14, ctypes.c_int) 2139 | VIDIOC_S_PARM_OLD = _IOW('V', 22, v4l2_streamparm) 2140 | VIDIOC_S_CTRL_OLD = _IOW('V', 28, v4l2_control) 2141 | VIDIOC_G_AUDIO_OLD = _IOWR('V', 33, v4l2_audio) 2142 | VIDIOC_G_AUDOUT_OLD = _IOWR('V', 49, v4l2_audioout) 2143 | VIDIOC_CROPCAP_OLD = _IOR('V', 58, v4l2_cropcap) 2144 | 2145 | BASE_VIDIOC_PRIVATE = 192 2146 | --------------------------------------------------------------------------------