Swiftgram/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift
2024-03-20 17:21:16 +04:00

211 lines
6.9 KiB
Swift

import Foundation
import UIKit
import CoreMedia
import FFMpegBinding
import YuvConversion
final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter {
public static let registerFFMpegGlobals: Void = {
FFMpegGlobals.initializeGlobals()
return
}()
var ffmpegWriter: FFMpegVideoWriter?
var pool: CVPixelBufferPool?
var secondPool: CVPixelBufferPool?
func setup(configuration: MediaEditorVideoExport.Configuration, outputPath: String) {
let _ = MediaEditorVideoFFMpegWriter.registerFFMpegGlobals
let width = Int32(configuration.dimensions.width)
let height = Int32(configuration.dimensions.height)
let bufferOptions: [String: Any] = [
kCVPixelBufferPoolMinimumBufferCountKey as String: 3 as NSNumber
]
let pixelBufferOptions: [String: Any] = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA as NSNumber,
kCVPixelBufferWidthKey as String: UInt32(width),
kCVPixelBufferHeightKey as String: UInt32(height)//,
// kCVPixelBufferIOSurfacePropertiesKey as String: [:] as NSDictionary
]
var pool: CVPixelBufferPool?
CVPixelBufferPoolCreate(nil, bufferOptions as CFDictionary, pixelBufferOptions as CFDictionary, &pool)
guard let pool else {
self.status = .failed
return
}
self.pool = pool
let secondPixelBufferOptions: [String: Any] = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8VideoRange_8A_TriPlanar as NSNumber,
kCVPixelBufferWidthKey as String: UInt32(width),
kCVPixelBufferHeightKey as String: UInt32(height)//,
// kCVPixelBufferIOSurfacePropertiesKey as String: [:] as NSDictionary
]
var secondPool: CVPixelBufferPool?
CVPixelBufferPoolCreate(nil, bufferOptions as CFDictionary, secondPixelBufferOptions as CFDictionary, &secondPool)
guard let secondPool else {
self.status = .failed
return
}
self.secondPool = secondPool
let ffmpegWriter = FFMpegVideoWriter()
self.ffmpegWriter = ffmpegWriter
if !ffmpegWriter.setup(withOutputPath: outputPath, width: width, height: height) {
self.status = .failed
}
}
func setupVideoInput(configuration: MediaEditorVideoExport.Configuration, preferredTransform: CGAffineTransform?, sourceFrameRate: Float) {
}
func setupAudioInput(configuration: MediaEditorVideoExport.Configuration) {
}
func startWriting() -> Bool {
if self.status != .failed {
self.status = .writing
return true
} else {
return false
}
}
func startSession(atSourceTime time: CMTime) {
}
func finishWriting(completion: @escaping () -> Void) {
guard let ffmpegWriter = self.ffmpegWriter else {
return
}
ffmpegWriter.finalizeVideo()
self.status = .completed
completion()
}
func cancelWriting() {
}
func requestVideoDataWhenReady(on queue: DispatchQueue, using block: @escaping () -> Void) {
queue.async {
block()
}
}
func requestAudioDataWhenReady(on queue: DispatchQueue, using block: @escaping () -> Void) {
}
var isReadyForMoreVideoData: Bool {
return true
}
func appendVideoBuffer(_ buffer: CMSampleBuffer) -> Bool {
return false
}
var isFirst = true
func appendPixelBuffer(_ buffer: CVPixelBuffer, at time: CMTime) -> Bool {
guard let ffmpegWriter = self.ffmpegWriter, let secondPool = self.secondPool else {
return false
}
let width = Int32(CVPixelBufferGetWidth(buffer))
let height = Int32(CVPixelBufferGetHeight(buffer))
let bytesPerRow = Int32(CVPixelBufferGetBytesPerRow(buffer))
var convertedBuffer: CVPixelBuffer?
CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, secondPool, &convertedBuffer)
guard let convertedBuffer else {
return false
}
CVPixelBufferLockBaseAddress(buffer, CVPixelBufferLockFlags(rawValue: 0))
let src = CVPixelBufferGetBaseAddress(buffer)
CVPixelBufferLockBaseAddress(convertedBuffer, CVPixelBufferLockFlags(rawValue: 0))
let dst = CVPixelBufferGetBaseAddress(convertedBuffer)
encodeRGBAToYUVA(dst, src, width, height, bytesPerRow, false, false)
CVPixelBufferUnlockBaseAddress(convertedBuffer, CVPixelBufferLockFlags(rawValue: 0))
CVPixelBufferUnlockBaseAddress(buffer, CVPixelBufferLockFlags(rawValue: 0))
if self.isFirst {
let path = NSTemporaryDirectory() + "test.png"
let image = self.imageFromCVPixelBuffer(convertedBuffer, orientation: .up)
let data = image?.pngData()
try? data?.write(to: URL(fileURLWithPath: path))
self.isFirst = false
}
return ffmpegWriter.encodeFrame(convertedBuffer)
}
func imageFromCVPixelBuffer(_ pixelBuffer: CVPixelBuffer, orientation: UIImage.Orientation) -> UIImage? {
CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
let width = CVPixelBufferGetWidth(pixelBuffer)
let height = CVPixelBufferGetHeight(pixelBuffer)
let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard let context = CGContext(
data: baseAddress,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
) else {
CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)
return nil
}
guard let cgImage = context.makeImage() else {
CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)
return nil
}
CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
func markVideoAsFinished() {
}
var pixelBufferPool: CVPixelBufferPool? {
return self.pool
}
var isReadyForMoreAudioData: Bool {
return false
}
func appendAudioBuffer(_ buffer: CMSampleBuffer) -> Bool {
return false
}
func markAudioAsFinished() {
}
var status: ExportWriterStatus = .unknown
var error: Error?
}