From 32773a08eb11d286682ca8cad41171ce804f1631 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Wed, 31 May 2023 15:39:59 +0200 Subject: Works --- CCCB Display/AppDelegate.swift | 1 + .../AppIcon.appiconset/Contents.json | 1 + .../AppIcon.appiconset/IMG_4898.jpeg | Bin 0 -> 534617 bytes .../IMG_4898.imageset/Contents.json | 21 ++ .../IMG_4898.imageset/IMG_4898.jpeg | Bin 0 -> 534617 bytes CCCB Display/Base.lproj/LaunchScreen.storyboard | 29 +- CCCB Display/Base.lproj/Main.storyboard | 129 +++++++- CCCB Display/ConfigController.swift | 35 +++ CCCB Display/ViewController.swift | 336 ++++++++++++++++++++- 9 files changed, 539 insertions(+), 13 deletions(-) create mode 100644 CCCB Display/Assets.xcassets/AppIcon.appiconset/IMG_4898.jpeg create mode 100644 CCCB Display/Assets.xcassets/IMG_4898.imageset/Contents.json create mode 100644 CCCB Display/Assets.xcassets/IMG_4898.imageset/IMG_4898.jpeg create mode 100644 CCCB Display/ConfigController.swift (limited to 'CCCB Display') diff --git a/CCCB Display/AppDelegate.swift b/CCCB Display/AppDelegate.swift index 8639c64..a3e61dc 100644 --- a/CCCB Display/AppDelegate.swift +++ b/CCCB Display/AppDelegate.swift @@ -14,6 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + UIApplication.shared.isIdleTimerDisabled = true return true } diff --git a/CCCB Display/Assets.xcassets/AppIcon.appiconset/Contents.json b/CCCB Display/Assets.xcassets/AppIcon.appiconset/Contents.json index 13613e3..bfe77c1 100644 --- a/CCCB Display/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/CCCB Display/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "IMG_4898.jpeg", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/CCCB Display/Assets.xcassets/AppIcon.appiconset/IMG_4898.jpeg b/CCCB Display/Assets.xcassets/AppIcon.appiconset/IMG_4898.jpeg new file mode 100644 index 0000000..b9f2603 Binary files /dev/null and b/CCCB Display/Assets.xcassets/AppIcon.appiconset/IMG_4898.jpeg differ diff --git a/CCCB Display/Assets.xcassets/IMG_4898.imageset/Contents.json b/CCCB Display/Assets.xcassets/IMG_4898.imageset/Contents.json new file mode 100644 index 0000000..ce6efb2 --- /dev/null +++ b/CCCB Display/Assets.xcassets/IMG_4898.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "IMG_4898.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CCCB Display/Assets.xcassets/IMG_4898.imageset/IMG_4898.jpeg b/CCCB Display/Assets.xcassets/IMG_4898.imageset/IMG_4898.jpeg new file mode 100644 index 0000000..b9f2603 Binary files /dev/null and b/CCCB Display/Assets.xcassets/IMG_4898.imageset/IMG_4898.jpeg differ diff --git a/CCCB Display/Base.lproj/LaunchScreen.storyboard b/CCCB Display/Base.lproj/LaunchScreen.storyboard index 865e932..37c47c1 100644 --- a/CCCB Display/Base.lproj/LaunchScreen.storyboard +++ b/CCCB Display/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -11,10 +13,21 @@ - + - + + + + + + + + + + + + @@ -22,4 +35,10 @@ + + + + + + diff --git a/CCCB Display/Base.lproj/Main.storyboard b/CCCB Display/Base.lproj/Main.storyboard index 25a7638..247b634 100644 --- a/CCCB Display/Base.lproj/Main.storyboard +++ b/CCCB Display/Base.lproj/Main.storyboard @@ -1,24 +1,143 @@ - + + - + + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CCCB Display/ConfigController.swift b/CCCB Display/ConfigController.swift new file mode 100644 index 0000000..92b6e37 --- /dev/null +++ b/CCCB Display/ConfigController.swift @@ -0,0 +1,35 @@ +// +// ConfigController.swift +// CCCB Display +// +// Created by Dirk Engling on 26.05.23. +// + +import Foundation +import UIKit + + +class ConfigController: UIViewController { + + @IBOutlet weak var display_address: UITextField! + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + let defaults = UserDefaults.standard + if let ip = defaults.string(forKey: "Display_Address") { + display_address.text = ip + } else { + display_address.text = "172.23.42.29" + } + } + + @IBAction func cancelPressed(_ sender: Any) { + self.dismiss(animated: true) + } + + @IBAction func donePressed(_ sender: Any) { + self.dismiss(animated: true) + let defaults = UserDefaults.standard + defaults.set(display_address.text, forKey: "Display_Address") + } +} diff --git a/CCCB Display/ViewController.swift b/CCCB Display/ViewController.swift index 53f3cee..d0f8dbe 100644 --- a/CCCB Display/ViewController.swift +++ b/CCCB Display/ViewController.swift @@ -6,14 +6,344 @@ // import UIKit +import AVFoundation +import CoreImage +import Network -class ViewController: UIViewController { +class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ObservableObject, AVCaptureVideoDataOutputSampleBufferDelegate { + + @IBOutlet weak var frameRateLabel: UILabel! + @IBOutlet weak var cameraView: UIView! + + var device: AVCaptureDevice? + var input: AVCaptureDeviceInput? + var prevLayer: AVCaptureVideoPreviewLayer? + + private let captureSession = AVCaptureSession() + private let videoDataOutput = AVCaptureVideoDataOutput() + private let sessionQueue = DispatchQueue(label: "sessionQueue") + private let context = CIContext() + +// var hostUDP: NWEndpoint.Host = "172.23.42.29" + var hostUDP: NWEndpoint.Host? + var portUDP: NWEndpoint.Port = 2342 + var connectionUDP: NWConnection? + + var lastTimeStamp: CFTimeInterval = CACurrentMediaTime() + + /* Physical Display control packet parameters: */ + private let HEADERLEN = 10 + private let WIDTH = 448 + private let HEIGHT = 160 + private let VHEIGHT = 236 override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view. + + UserDefaults.standard.addObserver(self, forKeyPath: "Display_Address", options: .new, context: nil) + constructSocket() + + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: // the user has already authorized to access the camera. + DispatchQueue.main.async { + self.createSession() + } + case .notDetermined: + AVCaptureDevice.requestAccess (for: .video) { (granted) in + if granted { + print("the user has granted to access the camera") + DispatchQueue.main.async { + self.createSession () + } + } else { + print("the user has not granted to access the camera") + let dialogMessage = UIAlertController(title: "Attention", message: "Can not work without camera access", preferredStyle: .alert) + self.present(dialogMessage, animated: true, completion: nil) + } + } + case .denied: + print("the user has denied previously to access the camera.") + let dialogMessage = UIAlertController(title: "Attention", message: "Can not work without camera access", preferredStyle: .alert) + self.present(dialogMessage, animated: true, completion: nil) + + case .restricted: + print("the user can't give camera access due to some restriction.") + let dialogMessage = UIAlertController(title: "Attention", message: "Can not work without camera access", preferredStyle: .alert) + self.present(dialogMessage, animated: true, completion: nil) + + default: + print("something has wrong due to we can't access the camera.") + let dialogMessage = UIAlertController(title: "Attention", message: "Can not work without camera access", preferredStyle: .alert) + self.present(dialogMessage, animated: true, completion: nil) + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + prevLayer?.frame.size = cameraView.frame.size } + func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { -} + let now = CACurrentMediaTime() + let freq = (Int)(1 / (now - lastTimeStamp)) +// print ("Elapsed: \(now - lastTimeStamp) - Frequency: \(1 / (now - lastTimeStamp))") + lastTimeStamp = now + + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } + + CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) + let bufferWidth = CVPixelBufferGetWidth(pixelBuffer) + let bufferHeight = CVPixelBufferGetHeight(pixelBuffer) + let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer) + let kBytesPerPixel = 4 + + print("\(bufferWidth) \(bufferHeight) \(bytesPerRow)") + guard let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) else { return } + + var packet: [UInt8] = [0, 0x12, 0, 0, 0x23, 0, 0, 0, 0, 0] + var scratch: [Int] = Array(repeating: 0, count: WIDTH*(2+VHEIGHT)) + + let t1 = CACurrentMediaTime() + + // 160 real rows are interleaved with 19 gaps of 4 pixels height on the display + // so we create 20 virtual blocks of 8 real and 4 virtual pixels + // we overlay VHEIGHT==236 virtual rows on the image and later skip the 4 invisble rows + var off = 0 + for row in 0..> 23) + accv += 1 + if accv == 8 { + packet.append((UInt8)(acc)) + acc = 0 + accv = 0 + } + } + + let err = (pixel - bwpixel) / 42 + + func AddSatShift(_ scr: inout Array, _ X: Int, _ Y: Int, _ SHIFT: Int) { + let inner_p = (row + Y) * WIDTH + column + X + var r = scr[inner_p] + (err << (16 - SHIFT)) + if r < 0 { + r = 0 + } + if r > 0xffffff { + r = 0xffffff + } + scr[inner_p] = r + } + + AddSatShift(&scratch, 0, 1, 13) + AddSatShift(&scratch, 0, 2, 14) + if (column > 0) { + AddSatShift(&scratch, -1, 1, 14) + AddSatShift(&scratch, -1, 2, 15) + } + + if (column > 1) { + AddSatShift(&scratch, -2, 1, 15) + AddSatShift(&scratch, -2, 2, 16) + } + + if (column < WIDTH - 1) { + AddSatShift(&scratch, 1, 0, 13) + AddSatShift(&scratch, 1, 1, 14) + AddSatShift(&scratch, 1, 2, 15) + } + + if (column < WIDTH - 2) { + AddSatShift(&scratch, 2, 0, 14) + AddSatShift(&scratch, 2, 1, 15) + AddSatShift(&scratch, 2, 2, 16) + } + + } + } + + let t2 = CACurrentMediaTime() + +// print("dur \(t2 - t1)") + DispatchQueue.main.async { + self.frameRateLabel.text = String(format: "%.04f (%d Hz)", t2 - t1, freq) + } + + self.connectionUDP?.send(content: packet, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in + if (NWError == nil) { + print("Data was sent to UDP") + } else { + print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)") + self.constructSocket() + } + }))) + } + func createSession() { + guard let device = AVCaptureDevice.default(for: AVMediaType.video) else { return } + do { + input = try AVCaptureDeviceInput(device: device) + } + catch { + print(error) + } + + captureSession.sessionPreset = AVCaptureSession.Preset.vga640x480 + if let input = input { + captureSession.addInput(input) + } + + prevLayer = AVCaptureVideoPreviewLayer(session: captureSession) + prevLayer?.frame.size = cameraView.frame.size + prevLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill + + cameraView.layer.addSublayer(prevLayer!) + captureSession.addOutput(videoDataOutput) + captureSession.commitConfiguration() + + videoDataOutput.videoSettings.updateValue(kCVPixelFormatType_32BGRA, forKey: "PixelFormatType") + videoDataOutput.setSampleBufferDelegate(self, queue: self.sessionQueue) + + do { + try device.lockForConfiguration() + device.activeVideoMaxFrameDuration = CMTimeMake(value: 1, timescale: 60) + device.activeVideoMinFrameDuration = CMTimeMake(value: 1, timescale: 60) + device.unlockForConfiguration() + } catch { + print(error) + } + + let captureConnection = videoDataOutput.connection(with: .video) + captureConnection?.isEnabled = true + deviceOrientationDidChange(Notification(name: UIDevice.orientationDidChangeNotification)) + // captureConnection?.videoOrientation = .landscapeRight + + sessionQueue.async { self.captureSession.startRunning() } + } + + func cameraWithPosition(position: AVCaptureDevice.Position) -> AVCaptureDevice? { + if #available(iOS 11.1, *) { + let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInTelephotoCamera, .builtInTrueDepthCamera, .builtInWideAngleCamera, ], mediaType: .video, position: position) + + if let device = deviceDiscoverySession.devices.first { + return device + } + else { + //add code here + } + return nil + } + + return device + } + + func transformOrientation(orientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation { + switch orientation { + case .landscapeLeft: + return .landscapeLeft + case .landscapeRight: + return .landscapeRight + case .portraitUpsideDown: + return .portraitUpsideDown + default: + return .portrait + } + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "Display_Address" { + constructSocket() + } + } + + func constructSocket() { + let defaults = UserDefaults.standard + if let ip = defaults.string(forKey: "Display_Address") { + hostUDP = NWEndpoint.Host(ip) + } else { + hostUDP = NWEndpoint.Host("172.23.42.29") + // hostUDP = NWEndpoint.Host("84.200.61.9") + // hostUDP = NWEndpoint.Host("192.168.178.69") + } +// hostUDP = NWEndpoint.Host("192.168.178.69") + + self.connectionUDP = NWConnection(host: hostUDP!, port: portUDP, using: .udp) + self.connectionUDP?.start(queue: .global()) + } + + @IBAction func switchCameraSide(sender: AnyObject) { + let currentCameraInput: AVCaptureInput = captureSession.inputs[0] + captureSession.removeInput(currentCameraInput) + var newCamera: AVCaptureDevice + if (currentCameraInput as! AVCaptureDeviceInput).device.position == .back { + newCamera = self.cameraWithPosition(position: .front)! + } else { + newCamera = self.cameraWithPosition(position: .back)! + } + + var newVideoInput: AVCaptureDeviceInput? + do{ + newVideoInput = try AVCaptureDeviceInput(device: newCamera) + } + catch{ + print(error) + } + + if let newVideoInput = newVideoInput{ + captureSession.addInput(newVideoInput) + deviceOrientationDidChange(Notification(name: UIDevice.orientationDidChangeNotification)) + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), + name: UIDevice.orientationDidChangeNotification, object: nil) + deviceOrientationDidChange(Notification(name: UIDevice.orientationDidChangeNotification)) + + sessionQueue.async { self.captureSession.startRunning() } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + sessionQueue.async { self.captureSession.stopRunning() } + NotificationCenter.default.removeObserver(self) + } + + @objc func deviceOrientationDidChange(_ notification: Notification) { + let orientation = UIDevice.current.orientation + let captureConnection = videoDataOutput.connection(with: .video) + + if orientation == .landscapeLeft { + self.prevLayer?.connection?.videoOrientation = .landscapeRight + captureConnection?.videoOrientation = .landscapeRight + } else { + self.prevLayer?.connection?.videoOrientation = .landscapeLeft + captureConnection?.videoOrientation = .landscapeLeft + } + } +} -- cgit v1.2.3