mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
228 lines
9.1 KiB
Swift
228 lines
9.1 KiB
Swift
import CoreMedia
|
|
import Accelerate
|
|
import FFMpeg
|
|
|
|
private let bufferCount = 32
|
|
|
|
final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
|
|
private let codecContext: FFMpegAVCodecContext
|
|
|
|
private let videoFrame: FFMpegAVFrame
|
|
private var resetDecoderOnNextFrame = true
|
|
|
|
private var pixelBufferPool: CVPixelBufferPool?
|
|
|
|
private var delayedFrames: [MediaTrackFrame] = []
|
|
|
|
init(codecContext: FFMpegAVCodecContext) {
|
|
self.codecContext = codecContext
|
|
self.videoFrame = FFMpegAVFrame()
|
|
|
|
/*var sourcePixelBufferOptions: [String: Any] = [:]
|
|
sourcePixelBufferOptions[kCVPixelBufferPixelFormatTypeKey as String] = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange as NSNumber
|
|
|
|
sourcePixelBufferOptions[kCVPixelBufferWidthKey as String] = codecContext.pointee.width as NSNumber
|
|
sourcePixelBufferOptions[kCVPixelBufferHeightKey as String] = codecContext.pointee.height as NSNumber
|
|
sourcePixelBufferOptions[kCVPixelBufferBytesPerRowAlignmentKey as String] = 128 as NSNumber
|
|
sourcePixelBufferOptions[kCVPixelBufferPlaneAlignmentKey as String] = 128 as NSNumber
|
|
|
|
let ioSurfaceProperties = NSMutableDictionary()
|
|
ioSurfaceProperties["IOSurfaceIsGlobal"] = true as NSNumber
|
|
|
|
sourcePixelBufferOptions[kCVPixelBufferIOSurfacePropertiesKey as String] = ioSurfaceProperties
|
|
|
|
var pixelBufferPoolOptions: [String: Any] = [:]
|
|
pixelBufferPoolOptions[kCVPixelBufferPoolMinimumBufferCountKey as String] = bufferCount as NSNumber
|
|
|
|
var pixelBufferPool: CVPixelBufferPool?
|
|
CVPixelBufferPoolCreate(kCFAllocatorDefault, pixelBufferPoolOptions as CFDictionary, sourcePixelBufferOptions as CFDictionary, &pixelBufferPool)
|
|
|
|
self.pixelBufferPool = pixelBufferPool*/
|
|
}
|
|
|
|
func decodeInternal(frame: MediaTrackDecodableFrame) {
|
|
|
|
}
|
|
|
|
func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? {
|
|
return self.decode(frame: frame, ptsOffset: nil)
|
|
}
|
|
|
|
func decode(frame: MediaTrackDecodableFrame, ptsOffset: CMTime?) -> MediaTrackFrame? {
|
|
var status = frame.packet.send(toDecoder: self.codecContext)
|
|
if status == 0 {
|
|
if self.codecContext.receive(into: self.videoFrame) {
|
|
var pts = CMTimeMake(self.videoFrame.pts, frame.pts.timescale)
|
|
if let ptsOffset = ptsOffset {
|
|
pts = CMTimeAdd(pts, ptsOffset)
|
|
}
|
|
return convertVideoFrame(self.videoFrame, pts: pts, dts: pts, duration: frame.duration)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func takeRemainingFrame() -> MediaTrackFrame? {
|
|
if !self.delayedFrames.isEmpty {
|
|
var minFrameIndex = 0
|
|
var minPosition = self.delayedFrames[0].position
|
|
for i in 1 ..< self.delayedFrames.count {
|
|
if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 {
|
|
minFrameIndex = i
|
|
minPosition = self.delayedFrames[i].position
|
|
}
|
|
}
|
|
return self.delayedFrames.remove(at: minFrameIndex)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private func convertVideoFrame(_ frame: FFMpegAVFrame, pts: CMTime, dts: CMTime, duration: CMTime) -> MediaTrackFrame? {
|
|
if frame.data[0] == nil {
|
|
return nil
|
|
}
|
|
if frame.lineSize[1] != frame.lineSize[2] {
|
|
return nil
|
|
}
|
|
|
|
var pixelBufferRef: CVPixelBuffer?
|
|
if let pixelBufferPool = self.pixelBufferPool {
|
|
let auxAttributes: [String: Any] = [kCVPixelBufferPoolAllocationThresholdKey as String: bufferCount as NSNumber];
|
|
let err = CVPixelBufferPoolCreatePixelBufferWithAuxAttributes(kCFAllocatorDefault, pixelBufferPool, auxAttributes as CFDictionary, &pixelBufferRef)
|
|
if err == kCVReturnWouldExceedAllocationThreshold {
|
|
print("kCVReturnWouldExceedAllocationThreshold, dropping frame")
|
|
return nil
|
|
}
|
|
} else {
|
|
let ioSurfaceProperties = NSMutableDictionary()
|
|
ioSurfaceProperties["IOSurfaceIsGlobal"] = true as NSNumber
|
|
|
|
var options: [String: Any] = [kCVPixelBufferBytesPerRowAlignmentKey as String: frame.lineSize[0] as NSNumber]
|
|
/*if #available(iOSApplicationExtension 9.0, *) {
|
|
options[kCVPixelBufferOpenGLESTextureCacheCompatibilityKey as String] = true as NSNumber
|
|
}*/
|
|
options[kCVPixelBufferIOSurfacePropertiesKey as String] = ioSurfaceProperties
|
|
|
|
CVPixelBufferCreate(kCFAllocatorDefault,
|
|
Int(frame.width),
|
|
Int(frame.height),
|
|
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
|
|
options as CFDictionary,
|
|
&pixelBufferRef)
|
|
}
|
|
|
|
guard let pixelBuffer = pixelBufferRef else {
|
|
return nil
|
|
}
|
|
|
|
let srcPlaneSize = Int(frame.lineSize[1]) * Int(frame.height / 2)
|
|
let dstPlaneSize = srcPlaneSize * 2
|
|
|
|
let dstPlane = malloc(dstPlaneSize)!.assumingMemoryBound(to: UInt8.self)
|
|
defer {
|
|
free(dstPlane)
|
|
}
|
|
|
|
for i in 0 ..< srcPlaneSize {
|
|
dstPlane[2 * i] = frame.data[1]![i]
|
|
dstPlane[2 * i + 1] = frame.data[2]![i]
|
|
}
|
|
|
|
let status = CVPixelBufferLockBaseAddress(pixelBuffer, [])
|
|
if status != kCVReturnSuccess {
|
|
return nil
|
|
}
|
|
|
|
let bytePerRowY = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
|
|
|
|
let bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1)
|
|
|
|
var base = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)!
|
|
if bytePerRowY == frame.lineSize[0] {
|
|
memcpy(base, frame.data[0]!, bytePerRowY * Int(frame.height))
|
|
} else {
|
|
var dest = base
|
|
var src = frame.data[0]!
|
|
let linesize = Int(frame.lineSize[0])
|
|
for _ in 0 ..< Int(frame.height) {
|
|
memcpy(dest, src, linesize)
|
|
dest = dest.advanced(by: bytePerRowY)
|
|
src = src.advanced(by: linesize)
|
|
}
|
|
}
|
|
|
|
base = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1)!
|
|
if bytesPerRowUV == frame.lineSize[1] * 2 {
|
|
memcpy(base, dstPlane, Int(frame.height / 2) * bytesPerRowUV)
|
|
} else {
|
|
var dest = base
|
|
var src = dstPlane
|
|
let linesize = Int(frame.lineSize[1]) * 2
|
|
for _ in 0 ..< Int(frame.height / 2) {
|
|
memcpy(dest, src, linesize)
|
|
dest = dest.advanced(by: bytesPerRowUV)
|
|
src = src.advanced(by: linesize)
|
|
}
|
|
}
|
|
|
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, [])
|
|
|
|
var formatRef: CMVideoFormatDescription?
|
|
let formatStatus = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &formatRef)
|
|
|
|
guard let format = formatRef, formatStatus == 0 else {
|
|
return nil
|
|
}
|
|
|
|
var timingInfo = CMSampleTimingInfo(duration: duration, presentationTimeStamp: pts, decodeTimeStamp: pts)
|
|
var sampleBuffer: CMSampleBuffer?
|
|
|
|
guard CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, pixelBuffer, format, &timingInfo, &sampleBuffer) == noErr else {
|
|
return nil
|
|
}
|
|
|
|
let attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer!, true)! as NSArray
|
|
let dict = attachments[0] as! NSMutableDictionary
|
|
|
|
let resetDecoder = self.resetDecoderOnNextFrame
|
|
if self.resetDecoderOnNextFrame {
|
|
self.resetDecoderOnNextFrame = false
|
|
//dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleBufferAttachmentKey_ResetDecoderBeforeDecoding as NSString as String)
|
|
}
|
|
|
|
dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DisplayImmediately as NSString as String)
|
|
|
|
let decodedFrame = MediaTrackFrame(type: .video, sampleBuffer: sampleBuffer!, resetDecoder: resetDecoder, decoded: true)
|
|
|
|
self.delayedFrames.append(decodedFrame)
|
|
|
|
if self.delayedFrames.count >= 1 {
|
|
var minFrameIndex = 0
|
|
var minPosition = self.delayedFrames[0].position
|
|
for i in 1 ..< self.delayedFrames.count {
|
|
if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 {
|
|
minFrameIndex = i
|
|
minPosition = self.delayedFrames[i].position
|
|
}
|
|
}
|
|
if minFrameIndex != 0 {
|
|
assert(true)
|
|
}
|
|
return self.delayedFrames.remove(at: minFrameIndex)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func decodeImage() {
|
|
|
|
}
|
|
|
|
func reset() {
|
|
self.codecContext.flushBuffers()
|
|
self.resetDecoderOnNextFrame = true
|
|
}
|
|
}
|