#!/usr/bin/env python3 import subprocess import os import glob def get_orientation(): for device in glob.glob('/sys/bus/iio/devices/iio:device*'): if os.path.isfile(os.path.join(device, 'in_accel_x_raw')): break else: return 0 with open(os.path.join(device, 'in_accel_x_raw')) as handle: x = int(handle.read().strip()) with open(os.path.join(device, 'in_accel_y_raw')) as handle: y = int(handle.read().strip()) with open(os.path.join(device, 'in_accel_z_raw')) as handle: z = int(handle.read().strip()) max_abs = max(abs(x), abs(y), abs(z)) if x == max_abs: # portrait return "portrait" if -x == max_abs: # upside down portrait return "portrait" if y == max_abs: # landscale counterclockwise return "ccw" if -y == max_abs: # landscape clockwise return "cw" if z == max_abs: # flat on back return "flat" if -z == max_abs: # flat on screen return "flat" return "cw" def setup(node, res, debug=False, pixfmt=None): speed = 30 if '@' in res: res, speed = res.split('@') busfmts = { 'UYVY': 'UYVY8_2X8', 'BA81': 'SBGGR8_1X8', 'GBRG': 'SGBRG8_1X8', 'GRBG': 'SGRBG8_1X8', 'RGGB': 'SRGGB8_1X8', } if pixfmt in busfmts: busfmt = busfmts[pixfmt] else: busfmt = 'UYVY8_2X8' command = ['sudo', 'media-ctl', '-d', '/dev/media1', '--set-v4l2', '"{}":0[fmt:{}/{}@1/{}]'.format(node, busfmt, res, speed)] if debug: print(command) p = subprocess.run(command, timeout=5) if p.returncode != 0: return False width, height = res.split('x') fmt = ['width=' + width, 'height=' + height, 'pixelformat={}'.format(pixfmt)] command = ['sudo', 'v4l2-ctl', '--device', '/dev/video1', '--set-fmt-video={}'.format(','.join(fmt))] if debug: print(command) p = subprocess.run(command, timeout=5) if p.returncode != 0: return False def take_snapshot(node, res, name, rotate, skip=5, debug=False, pixfmt=None, raw=False): if pixfmt is None: pixfmt = 'uyvy' setup(node, res, debug=debug, pixfmt=pixfmt) speed = 30 if '@' in res: res, speed = res.split('@') command = ['sudo', 'v4l2-ctl', '--device', '/dev/video1', '--stream-mmap', '--stream-to=/tmp/frame.raw', '--stream-count=1', '--stream-skip={}'.format(skip)] if debug: print(command) p = subprocess.run(command, timeout=10) if p.returncode != 0: return False if raw: os.rename('/tmp/frame.raw', name) return True command = ['convert', '-size', res, 'uyvy:/tmp/frame.raw', '-rotate', rotate, name] if debug: print(command) p = subprocess.run(command, timeout=15) if p.returncode != 0: return False command = ['sudo', 'rm', '-rf', '/tmp/frame.raw'] if debug: print(command) subprocess.run(command, timeout=5) return True def record(node, res, name, rotate, debug=False): setup(node, res, debug=debug, pixfmt='uyvy') speed = 30 if '@' in res: res, speed = res.split('@') command = ['ffmpeg', '-f', 'v4l2', '-framerate', str(speed), '-video_size', res, '-i', '/dev/video1', '-preset', 'ultrafast', name] if debug: print(command) p = subprocess.run(command) if p.returncode != 0: return False def set_route(camera): if camera == 'ov5640': links = [ '"gc2145 3-003c":0->"sun6i-csi":0[0]', '"ov5640 3-004c":0->"sun6i-csi":0[1]' ] elif camera == 'gc2145': links = [ '"ov5640 3-004c":0->"sun6i-csi":0[0]', '"gc2145 3-003c":0->"sun6i-csi":0[1]' ] else: raise Exception("Something wrong") for link in links: subprocess.run(['sudo', 'media-ctl', '-d', '/dev/media1', '--links', link]) def main(): import argparse modes_ov5640 = { 'max': '1920x1080@20', '1080p': '1920x1080@20', '1080p20': '1920x1080@20', '1080p15': '1920x1080@15', '1080p5': '1920x1080@5', '720p': '1280x720@30', '720p60': '1280x720@60', '720p50': '1280x720@50', '720p30': '1280x720@30', '720p25': '1280x720@25', '720p24': '1280x720@24', } modes_gc2145 = { 'max': '1600x1200@15', '1200p': '1600x1200@15', '1200p15': '1600x1200@15', '1200p1': '1600x1200@1', '720p': '1280x720@30', '720p1': '1280x720@1', '1080p15': '1920x1080@15', } options = set(modes_ov5640.keys()) options.update(modes_gc2145.keys()) parser = argparse.ArgumentParser(description="PinePhone camera tool") parser.add_argument('action', choices=['still', 'movie']) parser.add_argument('--resolution', '-r', choices=options, default='1080p') parser.add_argument('--camera', '-c', choices=['rear', 'front'], default='rear') parser.add_argument('--debug', '-d', action="store_true") parser.add_argument('--raw', action="store_true", help="store raw frame") parser.add_argument('--pixfmt', '-p', help="pixelformat for raw frame", default="UYVY") parser.add_argument('filename') args = parser.parse_args() skip = 5 orientation = get_orientation() if args.camera == "rear": set_route("ov5640") node = 'ov5640 3-004c' if orientation == "portrait": angle = '90' elif orientation == "cw": angle = '0' elif orientation == "ccw": angle = '180' else: angle = '0' modes = modes_ov5640 else: set_route("gc2145") node = 'gc2145 3-003c' if orientation == "portrait": angle = '270' elif orientation == "cw": angle = '0' elif orientation == "ccw": angle = '180' else: angle = '0' modes = modes_gc2145 skip = 0 mode = modes[args.resolution] if args.action == "still": take_snapshot(node, mode, args.filename, angle, skip=skip, debug=args.debug, pixfmt=args.pixfmt, raw=args.raw) elif args.action == "movie": record(node, mode, args.filename, angle, debug=args.debug) else: print("Unsupported action") exit(1) if __name__ == "__main__": main()