mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
211 lines
6.9 KiB
Swift
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?
|
|
}
|