import Foundation import UIKit import AsyncDisplayKit import Display import SwiftSignalKit import AccountContext import TelegramVoip import AVFoundation import LibYuvBinding private func copyI420BufferToNV12Buffer(buffer: OngoingGroupCallContext.VideoFrameData.I420Buffer, pixelBuffer: CVPixelBuffer) -> Bool { guard CVPixelBufferGetPixelFormatType(pixelBuffer) == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange else { return false } guard CVPixelBufferGetWidthOfPlane(pixelBuffer, 0) == buffer.width else { return false } guard CVPixelBufferGetHeightOfPlane(pixelBuffer, 0) == buffer.height else { return false } let cvRet = CVPixelBufferLockBaseAddress(pixelBuffer, []) if cvRet != kCVReturnSuccess { return false } defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, []) } guard let dstY = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0) else { return false } let dstStrideY = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0) guard let dstUV = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1) else { return false } let dstStrideUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1) buffer.y.withUnsafeBytes { srcYBuffer in guard let srcY = srcYBuffer.baseAddress else { return } buffer.u.withUnsafeBytes { srcUBuffer in guard let srcU = srcUBuffer.baseAddress else { return } buffer.v.withUnsafeBytes { srcVBuffer in guard let srcV = srcVBuffer.baseAddress else { return } libyuv_I420ToNV12( srcY.assumingMemoryBound(to: UInt8.self), Int32(buffer.strideY), srcU.assumingMemoryBound(to: UInt8.self), Int32(buffer.strideU), srcV.assumingMemoryBound(to: UInt8.self), Int32(buffer.strideV), dstY.assumingMemoryBound(to: UInt8.self), Int32(dstStrideY), dstUV.assumingMemoryBound(to: UInt8.self), Int32(dstStrideUV), Int32(buffer.width), Int32(buffer.height) ) } } } return true } final class SampleBufferVideoRenderingView: UIView, VideoRenderingView { static override var layerClass: AnyClass { return AVSampleBufferDisplayLayer.self } var sampleBufferLayer: AVSampleBufferDisplayLayer { return self.layer as! AVSampleBufferDisplayLayer } private var isEnabled: Bool = false private var onFirstFrameReceived: ((Float) -> Void)? private var onOrientationUpdated: ((PresentationCallVideoView.Orientation, CGFloat) -> Void)? private var onIsMirroredUpdated: ((Bool) -> Void)? private var didReportFirstFrame: Bool = false private var currentOrientation: PresentationCallVideoView.Orientation = .rotation0 private var currentAspect: CGFloat = 1.0 private var disposable: Disposable? init(input: Signal) { super.init(frame: CGRect()) self.disposable = input.start(next: { [weak self] videoFrameData in Queue.mainQueue().async { self?.addFrame(videoFrameData) } }) self.sampleBufferLayer.videoGravity = .resize } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { self.disposable?.dispose() } private func addFrame(_ videoFrameData: OngoingGroupCallContext.VideoFrameData) { let aspect = CGFloat(videoFrameData.width) / CGFloat(videoFrameData.height) var isAspectUpdated = false if self.currentAspect != aspect { self.currentAspect = aspect isAspectUpdated = true } let videoFrameOrientation = PresentationCallVideoView.Orientation(videoFrameData.orientation) var isOrientationUpdated = false if self.currentOrientation != videoFrameOrientation { self.currentOrientation = videoFrameOrientation isOrientationUpdated = true } if isAspectUpdated || isOrientationUpdated { self.onOrientationUpdated?(self.currentOrientation, self.currentAspect) } if !self.didReportFirstFrame { self.didReportFirstFrame = true self.onFirstFrameReceived?(Float(self.currentAspect)) } if self.isEnabled { switch videoFrameData.buffer { case let .native(buffer): if let sampleBuffer = sampleBufferFromPixelBuffer(pixelBuffer: buffer.pixelBuffer) { self.sampleBufferLayer.enqueue(sampleBuffer) } case let .i420(buffer): let ioSurfaceProperties = NSMutableDictionary() let options = NSMutableDictionary() options.setObject(ioSurfaceProperties, forKey: kCVPixelBufferIOSurfacePropertiesKey as NSString) var pixelBuffer: CVPixelBuffer? CVPixelBufferCreate( kCFAllocatorDefault, buffer.width, buffer.height, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, options, &pixelBuffer ) if let pixelBuffer = pixelBuffer { if copyI420BufferToNV12Buffer(buffer: buffer, pixelBuffer: pixelBuffer) { if let sampleBuffer = sampleBufferFromPixelBuffer(pixelBuffer: pixelBuffer) { self.sampleBufferLayer.enqueue(sampleBuffer) } } } default: break } } } func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void) { self.onFirstFrameReceived = f self.didReportFirstFrame = false } func setOnOrientationUpdated(_ f: @escaping (PresentationCallVideoView.Orientation, CGFloat) -> Void) { self.onOrientationUpdated = f } func getOrientation() -> PresentationCallVideoView.Orientation { return self.currentOrientation } func getAspect() -> CGFloat { return self.currentAspect } func setOnIsMirroredUpdated(_ f: @escaping (Bool) -> Void) { self.onIsMirroredUpdated = f } func updateIsEnabled(_ isEnabled: Bool) { self.isEnabled = isEnabled } func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { } }