mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00

Added ability to download music without streaming Added progress indicators for various blocking tasks Fixed image gallery swipe to dismiss after zooming Added online member count indication in supergroups Fixed contact statuses in contact search
564 lines
28 KiB
Swift
564 lines
28 KiB
Swift
import Foundation
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import CoreMedia
|
|
import TelegramUIPrivateModule
|
|
import TelegramCore
|
|
|
|
private struct StreamContext {
|
|
let index: Int
|
|
let codecContext: UnsafeMutablePointer<AVCodecContext>?
|
|
let fps: CMTime
|
|
let timebase: CMTime
|
|
let duration: CMTime
|
|
let decoder: MediaTrackFrameDecoder
|
|
let rotationAngle: Double
|
|
let aspect: Double
|
|
}
|
|
|
|
struct FFMpegMediaFrameSourceDescription {
|
|
let duration: CMTime
|
|
let decoder: MediaTrackFrameDecoder
|
|
let rotationAngle: Double
|
|
let aspect: Double
|
|
}
|
|
|
|
struct FFMpegMediaFrameSourceDescriptionSet {
|
|
let audio: FFMpegMediaFrameSourceDescription?
|
|
let video: FFMpegMediaFrameSourceDescription?
|
|
}
|
|
|
|
private final class InitializedState {
|
|
fileprivate let avIoContext: UnsafeMutablePointer<AVIOContext>
|
|
fileprivate let avFormatContext: UnsafeMutablePointer<AVFormatContext>
|
|
|
|
fileprivate let audioStream: StreamContext?
|
|
fileprivate let videoStream: StreamContext?
|
|
|
|
init(avIoContext: UnsafeMutablePointer<AVIOContext>, avFormatContext: UnsafeMutablePointer<AVFormatContext>, audioStream: StreamContext?, videoStream: StreamContext?) {
|
|
self.avIoContext = avIoContext
|
|
self.avFormatContext = avFormatContext
|
|
self.audioStream = audioStream
|
|
self.videoStream = videoStream
|
|
}
|
|
|
|
deinit {
|
|
let avIoContext = Optional(self.avIoContext)
|
|
if self.avIoContext.pointee.buffer != nil {
|
|
av_free(self.avIoContext.pointee.buffer)
|
|
}
|
|
av_free(avIoContext)
|
|
|
|
var avFormatContext = Optional(self.avFormatContext)
|
|
avformat_close_input(&avFormatContext)
|
|
}
|
|
}
|
|
|
|
struct FFMpegMediaFrameSourceStreamContextInfo {
|
|
let duration: CMTime
|
|
let decoder: MediaTrackFrameDecoder
|
|
}
|
|
|
|
struct FFMpegMediaFrameSourceContextInfo {
|
|
let audioStream: FFMpegMediaFrameSourceStreamContextInfo?
|
|
let videoStream: FFMpegMediaFrameSourceStreamContextInfo?
|
|
}
|
|
|
|
private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer<UInt8>?, bufferSize: Int32) -> Int32 {
|
|
let context = Unmanaged<FFMpegMediaFrameSourceContext>.fromOpaque(userData!).takeUnretainedValue()
|
|
guard let postbox = context.postbox, let resourceReference = context.resourceReference, let streamable = context.streamable else {
|
|
return 0
|
|
}
|
|
|
|
var fetchedCount: Int32 = 0
|
|
|
|
var fetchedData: Data?
|
|
|
|
if streamable {
|
|
let data: Signal<Data, NoError>
|
|
let resourceSize: Int = resourceReference.resource.size ?? Int(Int32.max - 1)
|
|
let readCount = min(resourceSize - context.readingOffset, Int(bufferSize))
|
|
data = postbox.mediaBox.resourceData(resourceReference.resource, size: resourceSize, in: context.readingOffset ..< (context.readingOffset + readCount), mode: .complete)
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
if readCount == 0 {
|
|
fetchedData = Data()
|
|
} else {
|
|
let disposable = data.start(next: { data in
|
|
if data.count == readCount {
|
|
fetchedData = data
|
|
semaphore.signal()
|
|
}
|
|
})
|
|
semaphore.wait()
|
|
disposable.dispose()
|
|
}
|
|
} else {
|
|
let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false))
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
let readingOffset = context.readingOffset
|
|
let disposable = data.start(next: { next in
|
|
if next.complete {
|
|
let readCount = max(0, min(next.size - readingOffset, Int(bufferSize)))
|
|
let range = readingOffset ..< (readingOffset + readCount)
|
|
|
|
let fd = open(next.path, O_RDONLY, S_IRUSR)
|
|
if fd >= 0 {
|
|
lseek(fd, off_t(range.lowerBound), SEEK_SET)
|
|
var data = Data(count: readCount)
|
|
data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
|
let readBytes = read(fd, bytes, readCount)
|
|
assert(readBytes <= readCount)
|
|
}
|
|
fetchedData = data
|
|
close(fd)
|
|
}
|
|
semaphore.signal()
|
|
}
|
|
})
|
|
semaphore.wait()
|
|
disposable.dispose()
|
|
}
|
|
if let fetchedData = fetchedData {
|
|
fetchedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
|
memcpy(buffer, bytes, fetchedData.count)
|
|
}
|
|
fetchedCount = Int32(fetchedData.count)
|
|
context.readingOffset += Int(fetchedCount)
|
|
}
|
|
|
|
return fetchedCount
|
|
}
|
|
|
|
private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whence: Int32) -> Int64 {
|
|
let context = Unmanaged<FFMpegMediaFrameSourceContext>.fromOpaque(userData!).takeUnretainedValue()
|
|
guard let postbox = context.postbox, let resourceReference = context.resourceReference, let streamable = context.streamable, let statsCategory = context.statsCategory else {
|
|
return 0
|
|
}
|
|
|
|
var result: Int64 = offset
|
|
|
|
let resourceSize: Int
|
|
if let size = resourceReference.resource.size {
|
|
resourceSize = size
|
|
} else {
|
|
if !streamable {
|
|
var resultSize: Int = Int(Int32.max - 1)
|
|
let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false))
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
let disposable = data.start(next: { next in
|
|
if next.complete {
|
|
resultSize = Int(next.size)
|
|
semaphore.signal()
|
|
}
|
|
})
|
|
semaphore.wait()
|
|
disposable.dispose()
|
|
resourceSize = resultSize
|
|
} else {
|
|
resourceSize = Int(Int32.max - 1)
|
|
}
|
|
}
|
|
|
|
if (whence & AVSEEK_SIZE) != 0 {
|
|
result = Int64(resourceSize == Int(Int32.max - 1) ? -1 : resourceSize)
|
|
} else {
|
|
context.readingOffset = Int(min(Int64(resourceSize), offset))
|
|
|
|
if context.readingOffset != context.requestedDataOffset {
|
|
context.requestedDataOffset = context.readingOffset
|
|
|
|
if context.readingOffset >= resourceSize {
|
|
context.fetchedDataDisposable.set(nil)
|
|
context.requestedCompleteFetch = false
|
|
} else {
|
|
if streamable {
|
|
context.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, range: context.readingOffset ..< Int(Int32.max), statsCategory: statsCategory, preferBackgroundReferenceRevalidation: streamable).start())
|
|
} else if !context.requestedCompleteFetch && context.fetchAutomatically {
|
|
context.requestedCompleteFetch = true
|
|
context.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, statsCategory: statsCategory, preferBackgroundReferenceRevalidation: streamable).start())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
final class FFMpegMediaFrameSourceContext: NSObject {
|
|
private let thread: Thread
|
|
|
|
var closed = false
|
|
|
|
fileprivate var postbox: Postbox?
|
|
fileprivate var resourceReference: MediaResourceReference?
|
|
fileprivate var streamable: Bool?
|
|
fileprivate var statsCategory: MediaResourceStatsCategory?
|
|
|
|
private let ioBufferSize = 64 * 1024
|
|
fileprivate var readingOffset = 0
|
|
|
|
fileprivate var requestedDataOffset: Int?
|
|
fileprivate let fetchedDataDisposable = MetaDisposable()
|
|
fileprivate var requestedCompleteFetch = false
|
|
|
|
fileprivate var readingError = false
|
|
|
|
private var initializedState: InitializedState?
|
|
private var packetQueue: [FFMpegPacket] = []
|
|
|
|
private var preferSoftwareDecoding: Bool = false
|
|
fileprivate var fetchAutomatically: Bool = true
|
|
|
|
init(thread: Thread) {
|
|
self.thread = thread
|
|
}
|
|
|
|
deinit {
|
|
assert(Thread.current === self.thread)
|
|
|
|
fetchedDataDisposable.dispose()
|
|
}
|
|
|
|
func initializeState(postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, fetchAutomatically: Bool) {
|
|
if self.readingError || self.initializedState != nil {
|
|
return
|
|
}
|
|
|
|
let _ = FFMpegMediaFrameSourceContextHelpers.registerFFMpegGlobals
|
|
|
|
self.postbox = postbox
|
|
self.resourceReference = resourceReference
|
|
self.streamable = streamable
|
|
self.statsCategory = video ? .video : .audio
|
|
self.preferSoftwareDecoding = preferSoftwareDecoding
|
|
self.fetchAutomatically = fetchAutomatically
|
|
|
|
let resourceSize: Int = resourceReference.resource.size ?? Int(Int32.max - 1)
|
|
|
|
if streamable {
|
|
self.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, range: 0 ..< Int(Int32.max), statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start())
|
|
} else if !self.requestedCompleteFetch && self.fetchAutomatically {
|
|
self.requestedCompleteFetch = true
|
|
self.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start())
|
|
}
|
|
|
|
var avFormatContextRef = avformat_alloc_context()
|
|
guard let avFormatContext = avFormatContextRef else {
|
|
self.readingError = true
|
|
return
|
|
}
|
|
|
|
let avIoBuffer = av_malloc(self.ioBufferSize)!
|
|
let avIoContextRef = avio_alloc_context(avIoBuffer.assumingMemoryBound(to: UInt8.self), Int32(self.ioBufferSize), 0, Unmanaged.passUnretained(self).toOpaque(), readPacketCallback, nil, seekCallback)
|
|
|
|
guard let avIoContext = avIoContextRef else {
|
|
self.readingError = true
|
|
return
|
|
}
|
|
|
|
avFormatContext.pointee.pb = avIoContext
|
|
|
|
guard avformat_open_input(&avFormatContextRef, nil, nil, nil) >= 0 else {
|
|
self.readingError = true
|
|
return
|
|
}
|
|
|
|
guard avformat_find_stream_info(avFormatContext, nil) >= 0 else {
|
|
self.readingError = true
|
|
return
|
|
}
|
|
|
|
var videoStream: StreamContext?
|
|
var audioStream: StreamContext?
|
|
|
|
for streamIndex in FFMpegMediaFrameSourceContextHelpers.streamIndices(formatContext: avFormatContext, codecType: AVMEDIA_TYPE_VIDEO) {
|
|
if (avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.disposition & Int32(AV_DISPOSITION_ATTACHED_PIC)) == 0 {
|
|
|
|
let codecPar = avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.codecpar!
|
|
|
|
if self.preferSoftwareDecoding {
|
|
if let codec = avcodec_find_decoder(codecPar.pointee.codec_id) {
|
|
if let codecContext = avcodec_alloc_context3(codec) {
|
|
if avcodec_parameters_to_context(codecContext, avFormatContext.pointee.streams[streamIndex]!.pointee.codecpar) >= 0 {
|
|
if avcodec_open2(codecContext, codec, nil) >= 0 {
|
|
let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 24))
|
|
|
|
let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale)
|
|
|
|
var rotationAngle: Double = 0.0
|
|
if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value {
|
|
if strcmp(value, "0") != 0 {
|
|
if let angle = Double(String(cString: value)) {
|
|
rotationAngle = angle * Double.pi / 180.0
|
|
}
|
|
}
|
|
}
|
|
|
|
let aspect = Double(codecPar.pointee.width) / Double(codecPar.pointee.height)
|
|
|
|
videoStream = StreamContext(index: streamIndex, codecContext: codecContext, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect)
|
|
break
|
|
} else {
|
|
var codecContextRef: UnsafeMutablePointer<AVCodecContext>? = codecContext
|
|
avcodec_free_context(&codecContextRef)
|
|
}
|
|
} else {
|
|
var codecContextRef: UnsafeMutablePointer<AVCodecContext>? = codecContext
|
|
avcodec_free_context(&codecContextRef)
|
|
}
|
|
}
|
|
}
|
|
} else if codecPar.pointee.codec_id == AV_CODEC_ID_MPEG4 {
|
|
if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromMpeg4CodecData(UInt32(kCMVideoCodecType_MPEG4Video), codecPar.pointee.width, codecPar.pointee.height, codecPar.pointee.extradata, codecPar.pointee.extradata_size) {
|
|
let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 1000))
|
|
|
|
let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale)
|
|
|
|
var rotationAngle: Double = 0.0
|
|
if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value {
|
|
if strcmp(value, "0") != 0 {
|
|
if let angle = Double(String(cString: value)) {
|
|
rotationAngle = angle * Double.pi / 180.0
|
|
}
|
|
}
|
|
}
|
|
|
|
let aspect = Double(codecPar.pointee.width) / Double(codecPar.pointee.height)
|
|
|
|
videoStream = StreamContext(index: streamIndex, codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect)
|
|
break
|
|
}
|
|
} else if codecPar.pointee.codec_id == AV_CODEC_ID_H264 {
|
|
if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromAVCCodecData(UInt32(kCMVideoCodecType_H264), codecPar.pointee.width, codecPar.pointee.height, codecPar.pointee.extradata, codecPar.pointee.extradata_size) {
|
|
let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 1000))
|
|
|
|
let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale)
|
|
|
|
var rotationAngle: Double = 0.0
|
|
if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value {
|
|
if strcmp(value, "0") != 0 {
|
|
if let angle = Double(String(cString: value)) {
|
|
rotationAngle = angle * Double.pi / 180.0
|
|
}
|
|
}
|
|
}
|
|
|
|
let aspect = Double(codecPar.pointee.width) / Double(codecPar.pointee.height)
|
|
|
|
videoStream = StreamContext(index: streamIndex, codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect)
|
|
break
|
|
}
|
|
} else if codecPar.pointee.codec_id == AV_CODEC_ID_HEVC {
|
|
if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromHEVCCodecData(UInt32(kCMVideoCodecType_HEVC), codecPar.pointee.width, codecPar.pointee.height, codecPar.pointee.extradata, codecPar.pointee.extradata_size) {
|
|
let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 1000))
|
|
|
|
let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale)
|
|
|
|
var rotationAngle: Double = 0.0
|
|
if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value {
|
|
if strcmp(value, "0") != 0 {
|
|
if let angle = Double(String(cString: value)) {
|
|
rotationAngle = angle * Double.pi / 180.0
|
|
}
|
|
}
|
|
}
|
|
|
|
let aspect = Double(codecPar.pointee.width) / Double(codecPar.pointee.height)
|
|
|
|
videoStream = StreamContext(index: streamIndex, codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for streamIndex in FFMpegMediaFrameSourceContextHelpers.streamIndices(formatContext: avFormatContext, codecType: AVMEDIA_TYPE_AUDIO) {
|
|
if let codec = avcodec_find_decoder(avFormatContext.pointee.streams[streamIndex]!.pointee.codecpar.pointee.codec_id) {
|
|
if let codecContext = avcodec_alloc_context3(codec) {
|
|
if avcodec_parameters_to_context(codecContext, avFormatContext.pointee.streams[streamIndex]!.pointee.codecpar) >= 0 {
|
|
if avcodec_open2(codecContext, codec, nil) >= 0 {
|
|
let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 40000))
|
|
|
|
let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale)
|
|
|
|
audioStream = StreamContext(index: streamIndex, codecContext: codecContext, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegAudioFrameDecoder(codecContext: codecContext), rotationAngle: 0.0, aspect: 1.0)
|
|
} else {
|
|
var codecContextRef: UnsafeMutablePointer<AVCodecContext>? = codecContext
|
|
avcodec_free_context(&codecContextRef)
|
|
}
|
|
} else {
|
|
var codecContextRef: UnsafeMutablePointer<AVCodecContext>? = codecContext
|
|
avcodec_free_context(&codecContextRef)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.initializedState = InitializedState(avIoContext: avIoContext, avFormatContext: avFormatContext, audioStream: audioStream, videoStream: videoStream)
|
|
}
|
|
|
|
private func readPacket() -> FFMpegPacket? {
|
|
if !self.packetQueue.isEmpty {
|
|
return self.packetQueue.remove(at: 0)
|
|
} else {
|
|
return self.readPacketInternal()
|
|
}
|
|
}
|
|
|
|
private func readPacketInternal() -> FFMpegPacket? {
|
|
guard let initializedState = self.initializedState else {
|
|
return nil
|
|
}
|
|
|
|
let packet = FFMpegPacket()
|
|
if av_read_frame(initializedState.avFormatContext, &packet.packet) < 0 {
|
|
return nil
|
|
} else {
|
|
return packet
|
|
}
|
|
}
|
|
|
|
func takeFrames(until: Double) -> (frames: [MediaTrackDecodableFrame], endOfStream: Bool) {
|
|
if self.readingError {
|
|
return ([], true)
|
|
}
|
|
|
|
guard let initializedState = self.initializedState else {
|
|
return ([], true)
|
|
}
|
|
|
|
var videoTimestamp: Double?
|
|
if initializedState.videoStream == nil {
|
|
videoTimestamp = Double.infinity
|
|
}
|
|
|
|
var audioTimestamp: Double?
|
|
if initializedState.audioStream == nil {
|
|
audioTimestamp = Double.infinity
|
|
}
|
|
|
|
var frames: [MediaTrackDecodableFrame] = []
|
|
var endOfStream = false
|
|
|
|
while !self.readingError && ((videoTimestamp == nil || videoTimestamp!.isLess(than: until)) || (audioTimestamp == nil || audioTimestamp!.isLess(than: until))) {
|
|
|
|
if let packet = self.readPacket() {
|
|
if let videoStream = initializedState.videoStream, Int(packet.packet.stream_index) == videoStream.index {
|
|
let avNoPtsRawValue: UInt64 = 0x8000000000000000
|
|
let avNoPtsValue = Int64(bitPattern: avNoPtsRawValue)
|
|
let packetPts = packet.packet.pts == avNoPtsValue ? packet.packet.dts : packet.packet.pts
|
|
|
|
let pts = CMTimeMake(packetPts, videoStream.timebase.timescale)
|
|
let dts = CMTimeMake(packet.packet.dts, videoStream.timebase.timescale)
|
|
|
|
let duration: CMTime
|
|
|
|
let frameDuration = packet.packet.duration
|
|
if frameDuration != 0 {
|
|
duration = CMTimeMake(frameDuration * videoStream.timebase.value, videoStream.timebase.timescale)
|
|
} else {
|
|
duration = videoStream.fps
|
|
}
|
|
|
|
let frame = MediaTrackDecodableFrame(type: .video, packet: packet, pts: pts, dts: dts, duration: duration)
|
|
frames.append(frame)
|
|
|
|
if videoTimestamp == nil || videoTimestamp! < CMTimeGetSeconds(pts) {
|
|
videoTimestamp = CMTimeGetSeconds(pts)
|
|
}
|
|
} else if let audioStream = initializedState.audioStream, Int(packet.packet.stream_index) == audioStream.index {
|
|
let avNoPtsRawValue: UInt64 = 0x8000000000000000
|
|
let avNoPtsValue = Int64(bitPattern: avNoPtsRawValue)
|
|
let packetPts = packet.packet.pts == avNoPtsValue ? packet.packet.dts : packet.packet.pts
|
|
|
|
let pts = CMTimeMake(packetPts, audioStream.timebase.timescale)
|
|
let dts = CMTimeMake(packet.packet.dts, audioStream.timebase.timescale)
|
|
|
|
let duration: CMTime
|
|
|
|
let frameDuration = packet.packet.duration
|
|
if frameDuration != 0 {
|
|
duration = CMTimeMake(frameDuration * audioStream.timebase.value, audioStream.timebase.timescale)
|
|
} else {
|
|
duration = audioStream.fps
|
|
}
|
|
|
|
let frame = MediaTrackDecodableFrame(type: .audio, packet: packet, pts: pts, dts: dts, duration: duration)
|
|
frames.append(frame)
|
|
|
|
if audioTimestamp == nil || audioTimestamp! < CMTimeGetSeconds(pts) {
|
|
audioTimestamp = CMTimeGetSeconds(pts)
|
|
}
|
|
}
|
|
} else {
|
|
endOfStream = true
|
|
break
|
|
}
|
|
}
|
|
|
|
return (frames, endOfStream)
|
|
}
|
|
|
|
func contextInfo() -> FFMpegMediaFrameSourceContextInfo? {
|
|
if let initializedState = self.initializedState {
|
|
var audioStreamContext: FFMpegMediaFrameSourceStreamContextInfo?
|
|
var videoStreamContext: FFMpegMediaFrameSourceStreamContextInfo?
|
|
|
|
if let audioStream = initializedState.audioStream {
|
|
audioStreamContext = FFMpegMediaFrameSourceStreamContextInfo(duration: audioStream.duration, decoder: audioStream.decoder)
|
|
}
|
|
|
|
if let videoStream = initializedState.videoStream {
|
|
videoStreamContext = FFMpegMediaFrameSourceStreamContextInfo(duration: videoStream.duration, decoder: videoStream.decoder)
|
|
}
|
|
|
|
return FFMpegMediaFrameSourceContextInfo(audioStream: audioStreamContext, videoStream: videoStreamContext)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func seek(timestamp: Double, completed: (FFMpegMediaFrameSourceDescriptionSet, CMTime) -> Void) {
|
|
if let initializedState = self.initializedState {
|
|
self.packetQueue.removeAll()
|
|
|
|
for stream in [initializedState.videoStream, initializedState.audioStream] {
|
|
if let stream = stream {
|
|
let pts = CMTimeMakeWithSeconds(timestamp, stream.timebase.timescale)
|
|
av_seek_frame(initializedState.avFormatContext, Int32(stream.index), pts.value, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME)
|
|
break
|
|
}
|
|
}
|
|
|
|
var audioDescription: FFMpegMediaFrameSourceDescription?
|
|
var videoDescription: FFMpegMediaFrameSourceDescription?
|
|
|
|
if let audioStream = initializedState.audioStream {
|
|
audioDescription = FFMpegMediaFrameSourceDescription(duration: audioStream.duration, decoder: audioStream.decoder, rotationAngle: 0.0, aspect: 1.0)
|
|
}
|
|
|
|
if let videoStream = initializedState.videoStream {
|
|
videoDescription = FFMpegMediaFrameSourceDescription(duration: videoStream.duration, decoder: videoStream.decoder, rotationAngle: videoStream.rotationAngle, aspect: videoStream.aspect)
|
|
}
|
|
|
|
var actualPts: CMTime = CMTimeMake(0, 1)
|
|
for _ in 0 ..< 24 {
|
|
if let packet = self.readPacketInternal() {
|
|
if let videoStream = initializedState.videoStream, Int(packet.packet.stream_index) == videoStream.index {
|
|
self.packetQueue.append(packet)
|
|
actualPts = CMTimeMake(packet.pts, videoStream.timebase.timescale)
|
|
break
|
|
} else if let audioStream = initializedState.audioStream, Int(packet.packet.stream_index) == audioStream.index {
|
|
self.packetQueue.append(packet)
|
|
actualPts = CMTimeMake(packet.pts, audioStream.timebase.timescale)
|
|
break
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
completed(FFMpegMediaFrameSourceDescriptionSet(audio: audioDescription, video: videoDescription), actualPts)
|
|
}
|
|
}
|
|
}
|