Запись видео, отфильтрованного с помощью CIFilter, выполняется слишком медленно
Я пытаюсь обрезать и повысить качество видео во время записи. я использую AssetWriter
сохранить видео и CIFilters
для фильтрации в реальном времени.
Проблема в том, что запись действительно медленная.
Детали (первые 6 шагов - просто шаблонный код, поэтому не стесняйтесь их пропускать):
- Установите устройства, например, заднюю камеру и микрофон;
Настроить
CIContext
для использования графического процессора, как описано там:func setupContext() { // setup the GLKView for video/image preview OGLContext = EAGLContext(api: EAGLRenderingAPI.openGLES2) ciContext = CIContext.init(eaglContext: OGLContext!) }
Настройка
GLKView
;func setUpGLPreview(){ glView.context = OGLContext! glView.enableSetNeedsDisplay = false glView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2) glView.frame = CGRect(x: 0.0, y: 20.0, width: view.bounds.width, height: view.bounds.width * 9/16) glView.bounds = CGRect(x: 0.0, y: 0.0, width: view.bounds.width * 9/16, height: view.bounds.width) glView.contentMode = .scaleAspectFit view.updateConstraintsIfNeeded() glView.bindDrawable() previewBounds = CGRect(x: 0, y: 0, width: glView.drawableWidth, height: glView.drawableHeight) }
Настройка
AVCaptureSession
;Настройка входов для
AssetWriter
:func configureVideoSessionWithAsset(){ let compressionSettings: [String: Any] = [ AVVideoAverageBitRateKey: NSNumber(value: 20000000), AVVideoMaxKeyFrameIntervalKey: NSNumber(value: 1), AVVideoProfileLevelKey: AVVideoProfileLevelH264Baseline41 ] let videoSettings: [String : Any] = [ AVVideoCodecKey : AVVideoCodecH264, AVVideoCompressionPropertiesKey: compressionSettings, AVVideoWidthKey : 1080, AVVideoHeightKey : 1920, AVVideoScalingModeKey:AVVideoScalingModeResizeAspectFill ] assetWriterInputCamera = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings) let sourcePixelBufferAttributesDictionary : [String: Any] = [ String(kCVPixelBufferPixelFormatTypeKey) : kCVPixelFormatType_32BGRA, String(kCVPixelBufferWidthKey) : 1080, String(kCVPixelBufferHeightKey) : 1920, String(kCVPixelFormatOpenGLESCompatibility) : kCFBooleanTrue ] bufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterInputCamera!, sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary) assetWriterInputCamera?.expectsMediaDataInRealTime = true let transform = CGAffineTransform(rotationAngle: CGFloat.pi/2) assetWriterInputCamera?.transform = transform let audioSettings : [String : Any] = [ AVFormatIDKey : NSInteger(kAudioFormatMPEG4AAC), AVNumberOfChannelsKey : 2, AVSampleRateKey : NSNumber(value: 44100.0) ] assetWriterInputAudio = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: audioSettings) assetWriterInputAudio?.expectsMediaDataInRealTime = true }
Создание нового Writer каждый раз, когда мы начинаем захват:
func createNewWriter(){ let outputPath = NSTemporaryDirectory() + Date().description + "output" + "\(state.currentFileFormat.rawValue)" let outputFileURL = URL(fileURLWithPath: outputPath) let fileType = (state.currentFileFormat == .mov) ? AVFileTypeQuickTimeMovie : AVFileTypeMPEG4 self.assetWriter = try? AVAssetWriter(outputURL: outputFileURL, fileType: fileType) if let videoInput = assetWriterInputCamera{ self.assetWriter?.add(videoInput) } if let audioInput = assetWriterInputAudio{ self.assetWriter?.add(audioInput) } }
Наконец, запись и фильтрация:
var cropFilter = CIFilter(name: "CICrop") var scaleFilter = CIFilter(name: "CILanczosScaleTransform") func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) { sessionQueue.async { guard let imgBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), let previewBounds = self.previewBounds else{ return } //recording audio if self.state.isRecording{ if let audio = self.assetWriterInputAudio, connection.audioChannels.count > 0 && audio.isReadyForMoreMediaData { self.writerQueue.async { audio.append(sampleBuffer) } return } } let sourceImage = CIImage(cvPixelBuffer: imgBuffer) //setting drawRect let sourceAspect = sourceImage.extent.size.width / sourceImage.extent.size.height; let previewAspect = previewBounds.size.width / previewBounds.size.height; var drawRect = sourceImage.extent if (sourceAspect > previewAspect){ drawRect.size.width = drawRect.size.height * previewAspect } else{ drawRect.size.height = drawRect.size.width / previewAspect } //Not recording just showing the preview if(self.assetWriter == nil || self.assetWriter?.status == AVAssetWriterStatus.completed){ self.glView.bindDrawable() if(self.OGLContext != EAGLContext.current()){ EAGLContext.setCurrent(self.OGLContext) } glClearColor(0, 0, 0, 1.0) glClear(GLbitfield(GL_COLOR_BUFFER_BIT)) glEnable(GLenum(GL_BLEND)) glBlendFunc(GLenum(GL_ONE), GLenum(GL_ONE_MINUS_SRC_ALPHA)) self.ciContext?.draw(sourceImage, in: previewBounds, from: drawRect) self.glView.display() } else{ if !self.state.isRecording { return } let outputResolution = (self.state.currentCamera == .back) ? CGRect(x: 0,y: 0, width: 604, height: 1080) : CGRect(x: 0,y: 0, width: 405, height: 720) cropFilter?.setValue(sourceImage, forKey: kCIInputImageKey) cropFilter?.setValue(CIVector(cgRect: CGRect(x: 0, y: 0, width: outputResolution.width, height: outputResolution.height)), forKey: "inputRectangle") var filteredImage:CIImage? = sourceImage.cropping(to: outputResolution) scaleFilter?.setValue(filteredImage, forKey: kCIInputImageKey) scaleFilter?.setValue(1080/filteredImage!.extent.width, forKey: kCIInputScaleKey) filteredImage = scaleFilter?.outputImage //Checking if we started writing if self.assetWriter?.status == AVAssetWriterStatus.unknown{ if self.assetWriter?.startWriting != nil { self.assetWriter?.startWriting() self.assetWriter?.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) } } let time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) var newPixelBuffer:CVPixelBuffer? = nil guard let bufferPool = self.bufferAdaptor?.pixelBufferPool else{ return } if let filteredImage = filteredImage{ CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, bufferPool, &newPixelBuffer) self.ciContext?.render(filteredImage, to: newPixelBuffer!, bounds: filteredImage.extent, colorSpace: nil) self.glView.bindDrawable() glClearColor(0, 0, 0, 1.0) glClear(GLbitfield(GL_COLOR_BUFFER_BIT)) glEnable(GLenum(GL_BLEND)) glBlendFunc(GLenum(GL_ONE), GLenum(GL_ONE_MINUS_SRC_ALPHA)) self.ciContext?.draw(filteredImage, in: previewBounds, from: filteredImage.extent) self.glView.display() } if let camera = self.assetWriterInputCamera ,camera.isReadyForMoreMediaData{ self.writerQueue.async { self.bufferAdaptor?.append(newPixelBuffer!, withPresentationTime: time) } } } } }
Предварительный просмотр отлично работает до начала захвата. Во время захвата частота кадров резко падает до непригодного уровня.
Вопрос: что я делаю не так на последнем шаге? CIImage
фильтрация должна быть очень быстрой из-за использования графического процессора, но вместо этого все заморожено.
Пожалуйста, обратите внимание:
- Я знаю о
GPUImage
, но я думаю, что это избыточное решение для этой задачи; - Я проверил Apple
CIFunHouse
и проекты RosyWriter. Первый очень помог мне все настроить. Второй использует шейдеры для фильтрации видео, что немного сложнее для меня.
Любая помощь и комментарии приветствуются! Если у вас есть какие-либо вопросы по поводу кода, пожалуйста, задавайте!
Спасибо!