|
@@ -38,15 +38,14 @@
|
|
output中可以设置supportsRandomAccess,当为true时,可以重置读取范围,但需要调用方调用copyNextSampleBuffer,直到该方法返回NULL。
|
|
output中可以设置supportsRandomAccess,当为true时,可以重置读取范围,但需要调用方调用copyNextSampleBuffer,直到该方法返回NULL。
|
|
或者重新初始化一个AVAssetReader来设置读取时间。
|
|
或者重新初始化一个AVAssetReader来设置读取时间。
|
|
如果尝试第一种方案,需要使用seek,可以尝试每次设置一个不太长的区间,以保证读取完整个区间不会耗时太多,且时间间隔最好以关键帧划分。
|
|
如果尝试第一种方案,需要使用seek,可以尝试每次设置一个不太长的区间,以保证读取完整个区间不会耗时太多,且时间间隔最好以关键帧划分。
|
|
-
|
|
|
|
|
|
+
|
|
fps
|
|
fps
|
|
-
|
|
|
|
|
|
+
|
|
25.0 fps : 0.0000 0.0400 0.0800 0.1200 0.1600 0.2000 0.2400 0.2800 0.3200 0.3600 0.4000 0.4400 0.4800 0.5200 0.5600 0.6000 0.6400 0.6800 0.7200 0.7600 0.8000 0.8400 0.8800 0.9200 0.9600 1.0000 1.0400 1.0800 1.1200 1.1600 1.2000
|
|
25.0 fps : 0.0000 0.0400 0.0800 0.1200 0.1600 0.2000 0.2400 0.2800 0.3200 0.3600 0.4000 0.4400 0.4800 0.5200 0.5600 0.6000 0.6400 0.6800 0.7200 0.7600 0.8000 0.8400 0.8800 0.9200 0.9600 1.0000 1.0400 1.0800 1.1200 1.1600 1.2000
|
|
30.0 fps : 0.0000 0.0333 0.0667 0.1000 0.1333 0.1667 0.2000 0.2333 0.2667 0.3000 0.3333 0.3667 0.4000 0.4333 0.4667 0.5000 0.5333 0.5667 0.6000 0.6333 0.6667 0.7000 0.7333 0.7667 0.8000 0.8333 0.8667 0.9000 0.9333 0.9667 1.0000
|
|
30.0 fps : 0.0000 0.0333 0.0667 0.1000 0.1333 0.1667 0.2000 0.2333 0.2667 0.3000 0.3333 0.3667 0.4000 0.4333 0.4667 0.5000 0.5333 0.5667 0.6000 0.6333 0.6667 0.7000 0.7333 0.7667 0.8000 0.8333 0.8667 0.9000 0.9333 0.9667 1.0000
|
|
60.0 fps : 0.0000 0.0167 0.0333 0.0500 0.0667 0.0833 0.1000 0.1167 0.1333 0.1500 0.1667 0.1833 0.2000 0.2167 0.2333 0.2500 0.2667 0.2833 0.3000 0.3167 0.3333 0.3500 0.3667 0.3833 0.4000 0.4167 0.4333 0.4500 0.4667 0.4833 0.5000
|
|
60.0 fps : 0.0000 0.0167 0.0333 0.0500 0.0667 0.0833 0.1000 0.1167 0.1333 0.1500 0.1667 0.1833 0.2000 0.2167 0.2333 0.2500 0.2667 0.2833 0.3000 0.3167 0.3333 0.3500 0.3667 0.3833 0.4000 0.4167 0.4333 0.4500 0.4667 0.4833 0.5000
|
|
80.0 fps : 0.0000 0.0125 0.0250 0.0375 0.0500 0.0625 0.0750 0.0875 0.1000 0.1125 0.1250 0.1375 0.1500 0.1625 0.1750 0.1875 0.2000 0.2125 0.2250 0.2375 0.2500 0.2625 0.2750 0.2875 0.3000 0.3125 0.3250 0.3375 0.3500 0.3625 0.3750
|
|
80.0 fps : 0.0000 0.0125 0.0250 0.0375 0.0500 0.0625 0.0750 0.0875 0.1000 0.1125 0.1250 0.1375 0.1500 0.1625 0.1750 0.1875 0.2000 0.2125 0.2250 0.2375 0.2500 0.2625 0.2750 0.2875 0.3000 0.3125 0.3250 0.3375 0.3500 0.3625 0.3750
|
|
-120.0 fps : 0.0000 0.0083 0.0167 0.0250 0.0333 0.0417 0.0500 0.0583 0.0667 0.0750 0.0833 0.0917 0.1000 0.1083 0.1167 0.1250 0.1333 0.1417 0.1500 0.1583 0.1667 0.1750 0.1833 0.1917 0.2000 0.2083 0.2167 0.2250 0.2333 0.2417 0.2500
|
|
|
|
-
|
|
|
|
|
|
+ 120.0 fps : 0.0000 0.0083 0.0167 0.0250 0.0333 0.0417 0.0500 0.0583 0.0667 0.0750 0.0833 0.0917 0.1000 0.1083 0.1167 0.1250 0.1333 0.1417 0.1500 0.1583 0.1667 0.1750 0.1833 0.1917 0.2000 0.2083 0.2167 0.2250 0.2333 0.2417 0.2500
|
|
|
|
|
|
*/
|
|
*/
|
|
|
|
|
|
@@ -102,14 +101,13 @@ class PQMovieFilter: PQBaseFilter {
|
|
/// Use serial queue to ensure that the picture is smooth
|
|
/// Use serial queue to ensure that the picture is smooth
|
|
var seekQueue: DispatchQueue!
|
|
var seekQueue: DispatchQueue!
|
|
|
|
|
|
- // * 设置播放速率 范围 0 - 8(理论值) rate 正常速度为1.0;小于为慢速;大于为快速。但不能高于解码速度1-2ms硬解一帧
|
|
|
|
- var speedRate: Float = 1
|
|
|
|
-
|
|
|
|
// 原视频素材的 FPS
|
|
// 原视频素材的 FPS
|
|
var stickerFPS: Float = 0
|
|
var stickerFPS: Float = 0
|
|
|
|
|
|
// 开始时间,创建 filter 显示的时候有
|
|
// 开始时间,创建 filter 显示的时候有
|
|
var startTimeStamp: CMTime?
|
|
var startTimeStamp: CMTime?
|
|
|
|
+ // 最后一次显示帧时间戳
|
|
|
|
+ var targetTimeStamp: CMTime = .zero
|
|
|
|
|
|
deinit {
|
|
deinit {
|
|
FilterLog(message: "movie filter deinit")
|
|
FilterLog(message: "movie filter deinit")
|
|
@@ -196,23 +194,23 @@ class PQMovieFilter: PQBaseFilter {
|
|
releaseIncomingFramebuffers()
|
|
releaseIncomingFramebuffers()
|
|
|
|
|
|
// if CMTimeGetSeconds(currentTime) >= moveSticker!.timelineIn, CMTimeGetSeconds(currentTime) <= moveSticker!.timelineOut {
|
|
// if CMTimeGetSeconds(currentTime) >= moveSticker!.timelineIn, CMTimeGetSeconds(currentTime) <= moveSticker!.timelineOut {
|
|
- FilterLog(message: "开始显示 movefilter 了 开始\(String(describing: moveSticker?.timelineIn)) 结束 :\(String(describing: moveSticker?.timelineOut)) currentTime \(CMTimeGetSeconds(currentTime)) 裁剪开始时间:\(String(describing: moveSticker?.model_in)) ")
|
|
|
|
|
|
+ FilterLog(message: "开始显示 movefilter 了 开始\(String(describing: moveSticker?.timelineIn)) 结束 :\(String(describing: moveSticker?.timelineOut)) currentTime \(CMTimeGetSeconds(currentTime)) 裁剪开始时间:\(String(describing: moveSticker?.model_in)) ")
|
|
|
|
|
|
- if enableSeek {
|
|
|
|
- FilterLog(message: "seek 到 \(CMTimeGetSeconds(currentTime)) ")
|
|
|
|
- resetRangeTime(startTime: currentTime)
|
|
|
|
- enableSeek = false
|
|
|
|
- }
|
|
|
|
|
|
+ if enableSeek {
|
|
|
|
+ FilterLog(message: "seek 到 \(CMTimeGetSeconds(currentTime)) ")
|
|
|
|
+ resetRangeTime(startTime: currentTime)
|
|
|
|
+ enableSeek = false
|
|
|
|
+ }
|
|
|
|
|
|
- if startTimeStamp == nil {
|
|
|
|
- startTimeStamp = currentTime
|
|
|
|
- }
|
|
|
|
|
|
+ if startTimeStamp == nil {
|
|
|
|
+ startTimeStamp = currentTime
|
|
|
|
+ }
|
|
|
|
|
|
- let stickerTime = CMTime(value: Int64(moveSticker?.model_in ?? 0) * Int64(BASE_FILTER_TIMESCALE), timescale: BASE_FILTER_TIMESCALE)
|
|
|
|
|
|
+ let stickerTime = CMTime(value: Int64(moveSticker?.model_in ?? 0) * Int64(BASE_FILTER_TIMESCALE), timescale: BASE_FILTER_TIMESCALE)
|
|
|
|
|
|
- let PTSTime = CMTimeAdd(stickerTime, CMTimeSubtract(currentTime, startTimeStamp ?? .zero))
|
|
|
|
|
|
+ let PTSTime = CMTimeAdd(stickerTime, CMTimeSubtract(currentTime, startTimeStamp ?? .zero))
|
|
|
|
|
|
- readNextVideoFrame(showTimeStamp: CMTime(value: CMTimeValue(Int(Float(PTSTime.value) * speedRate)), timescale: PTSTime.timescale))
|
|
|
|
|
|
+ readNextVideoFrame(showTimeStamp: CMTime(value: CMTimeValue(Int(Float(PTSTime.value) * Float(stickerInfo!.speedRate))), timescale: PTSTime.timescale))
|
|
|
|
|
|
// } else {
|
|
// } else {
|
|
// FilterLog(message: "movefilter 了 结束了 timelineIN\(String(describing: moveSticker?.timelineIn)) timelineOut\(String(describing: moveSticker?.timelineOut)) currentTime \(CMTimeGetSeconds(currentTime)) 裁剪in:\(String(describing: moveSticker?.model_in)) 裁剪out:\(String(describing: moveSticker?.out)) ")
|
|
// FilterLog(message: "movefilter 了 结束了 timelineIN\(String(describing: moveSticker?.timelineIn)) timelineOut\(String(describing: moveSticker?.timelineOut)) currentTime \(CMTimeGetSeconds(currentTime)) 裁剪in:\(String(describing: moveSticker?.model_in)) 裁剪out:\(String(describing: moveSticker?.out)) ")
|
|
@@ -334,10 +332,15 @@ class PQMovieFilter: PQBaseFilter {
|
|
|
|
|
|
// 取出第一帧数据
|
|
// 取出第一帧数据
|
|
func readNextVideoFrame(showTimeStamp: CMTime) {
|
|
func readNextVideoFrame(showTimeStamp: CMTime) {
|
|
-
|
|
|
|
// XXXX 有时渲染视频取出来的画面时为黑屏,再渲染一次,数据是没有问题已经保存到沙盒进行验证,这个不是最好的方案!
|
|
// XXXX 有时渲染视频取出来的画面时为黑屏,再渲染一次,数据是没有问题已经保存到沙盒进行验证,这个不是最好的方案!
|
|
- if(lastImageBuffer != nil){
|
|
|
|
- self.renderPixelBuffler(movieFrame: lastImageBuffer!, withSampleTime: currentTime)
|
|
|
|
|
|
+ if lastImageBuffer != nil {
|
|
|
|
+ renderPixelBuffler(movieFrame: lastImageBuffer!, withSampleTime: currentTime)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 最后一帧的PTS > 要显示的目标时间 就不从解码器要数据,直接返回 view 不刷新 只有慢速时会调用
|
|
|
|
+ if CMTimeGetSeconds(targetTimeStamp) >= CMTimeGetSeconds(showTimeStamp) + (stickerInfo?.model_in ?? 0) && CMTimeGetSeconds(targetTimeStamp) != 0 {
|
|
|
|
+ FilterLog(message: "目标显示时间 \(String(format: "%.6f", (CMTimeGetSeconds(showTimeStamp)))) 最后显示的时间 \(String(format: "%.6f", CMTimeGetSeconds(targetTimeStamp) + (stickerInfo?.model_in ?? 0))) 裁剪开始时间:\(String(describing: moveSticker?.model_in)) speedRate is \(stickerInfo!.speedRate)")
|
|
|
|
+ return
|
|
}
|
|
}
|
|
|
|
|
|
if assetReader == nil {
|
|
if assetReader == nil {
|
|
@@ -365,21 +368,26 @@ class PQMovieFilter: PQBaseFilter {
|
|
FilterLog(message: " copyNextSampleBuffer is nil error!!!")
|
|
FilterLog(message: " copyNextSampleBuffer is nil error!!!")
|
|
return
|
|
return
|
|
}
|
|
}
|
|
- let targetTimeStamp = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer!)
|
|
|
|
|
|
+ targetTimeStamp = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer!)
|
|
|
|
|
|
// 目标帧 时间
|
|
// 目标帧 时间
|
|
if sampleBuffer != nil && CMTimeGetSeconds(targetTimeStamp) >= (CMTimeGetSeconds(showTimeStamp).truncatingRemainder(dividingBy: moveSticker!.out)) {
|
|
if sampleBuffer != nil && CMTimeGetSeconds(targetTimeStamp) >= (CMTimeGetSeconds(showTimeStamp).truncatingRemainder(dividingBy: moveSticker!.out)) {
|
|
- if sampleBuffer != nil {
|
|
|
|
- let endDecoderTime: TimeInterval = Date().timeIntervalSince1970
|
|
|
|
- FilterLog(message: "查找的帧时间为:\(CMTimeGetSeconds(showTimeStamp).truncatingRemainder(dividingBy: moveSticker!.out)) 命中时间为: \(CMTimeGetSeconds(targetTimeStamp)) 查找时长为\(TimeInterval(endDecoderTime - beginDecoderTime)) 查找次数\(count) 进场时间: \(String(describing: moveSticker?.timelineIn)) 裁剪开始时间:\(String(describing: moveSticker?.model_in))")
|
|
|
|
- break
|
|
|
|
- }
|
|
|
|
|
|
+ let endDecoderTime: TimeInterval = Date().timeIntervalSince1970
|
|
|
|
+ FilterLog(message: " speedRate is \(stickerInfo!.speedRate) 当前主线时间为:\(String(format: "%.6f", CMTimeGetSeconds(currentTime))) 查找的帧时间为:\(String(format: "%.6f", CMTimeGetSeconds(showTimeStamp).truncatingRemainder(dividingBy: moveSticker!.out))) 命中时间为: \(String(format: "%.6f", CMTimeGetSeconds(targetTimeStamp))) 查找耗时为:\(String(format: "%.6f", TimeInterval(endDecoderTime - beginDecoderTime))) 查找次数\(count) 进场时间: \(String(describing: moveSticker?.timelineIn)) 裁剪开始时间:\(String(describing: moveSticker?.model_in)) 裁剪结束时间:\(String(describing: moveSticker?.out))")
|
|
|
|
+ break
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ FilterLog(message: "不丢帧显示")
|
|
|
|
+// usleep(2)
|
|
|
|
+// sharedImageProcessingContext.runOperationSynchronously {
|
|
|
|
+// self.renderPixelBuffler(movieFrame: CMSampleBufferGetImageBuffer(sampleBuffer!)!, withSampleTime: currentTime)
|
|
|
|
+// }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 一,显示命中的帧数据
|
|
// 一,显示命中的帧数据
|
|
if sampleBuffer != nil {
|
|
if sampleBuffer != nil {
|
|
// if moveSticker?.materialDurationFit?.fitType == adapterMode.staticFrame.rawValue {
|
|
// if moveSticker?.materialDurationFit?.fitType == adapterMode.staticFrame.rawValue {
|
|
- lastImageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer!)!
|
|
|
|
|
|
+ lastImageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer!)!
|
|
// }
|
|
// }
|
|
|
|
|
|
sharedImageProcessingContext.runOperationSynchronously {
|
|
sharedImageProcessingContext.runOperationSynchronously {
|
|
@@ -388,7 +396,6 @@ class PQMovieFilter: PQBaseFilter {
|
|
return
|
|
return
|
|
} else {
|
|
} else {
|
|
FilterLog(message: "sampleBuffer is nil data is error self.assetReader?.status is \(String(describing: assetReader?.status))")
|
|
FilterLog(message: "sampleBuffer is nil data is error self.assetReader?.status is \(String(describing: assetReader?.status))")
|
|
-
|
|
|
|
}
|
|
}
|
|
// 二, 已经播放完一次
|
|
// 二, 已经播放完一次
|
|
if assetReader?.status == .completed {
|
|
if assetReader?.status == .completed {
|
|
@@ -418,20 +425,19 @@ class PQMovieFilter: PQBaseFilter {
|
|
/// - movieFrame:帧数据
|
|
/// - movieFrame:帧数据
|
|
/// - withSampleTime: 渲染时间戳,不是帧的 PTS 是渲染的时间
|
|
/// - withSampleTime: 渲染时间戳,不是帧的 PTS 是渲染的时间
|
|
func renderPixelBuffler(movieFrame: CVPixelBuffer, withSampleTime: CMTime) {
|
|
func renderPixelBuffler(movieFrame: CVPixelBuffer, withSampleTime: CMTime) {
|
|
-
|
|
|
|
- //NV12 会返回 2,Y分量和UV 分量, 如果buffer 是BGRA 则返回0
|
|
|
|
|
|
+ // NV12 会返回 2,Y分量和UV 分量, 如果buffer 是BGRA 则返回0
|
|
FilterLog(message: "CVPixelBufferGetPlaneCount is \(CVPixelBufferGetPlaneCount(movieFrame))")
|
|
FilterLog(message: "CVPixelBufferGetPlaneCount is \(CVPixelBufferGetPlaneCount(movieFrame))")
|
|
-
|
|
|
|
|
|
+
|
|
let bufferHeight = CVPixelBufferGetHeight(movieFrame)
|
|
let bufferHeight = CVPixelBufferGetHeight(movieFrame)
|
|
let bufferWidth = CVPixelBufferGetWidth(movieFrame)
|
|
let bufferWidth = CVPixelBufferGetWidth(movieFrame)
|
|
CVPixelBufferLockBaseAddress(movieFrame, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
|
|
CVPixelBufferLockBaseAddress(movieFrame, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
|
|
|
|
|
|
let conversionMatrix = colorConversionMatrix601FullRangeDefault
|
|
let conversionMatrix = colorConversionMatrix601FullRangeDefault
|
|
|
|
|
|
- //1 Y-plane
|
|
|
|
|
|
+ // 1 Y-plane
|
|
var luminanceGLTexture: CVOpenGLESTexture?
|
|
var luminanceGLTexture: CVOpenGLESTexture?
|
|
|
|
|
|
- //激活纹理
|
|
|
|
|
|
+ // 激活纹理
|
|
glActiveTexture(GLenum(GL_TEXTURE0))
|
|
glActiveTexture(GLenum(GL_TEXTURE0))
|
|
|
|
|
|
let luminanceGLTextureResult = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, sharedImageProcessingContext.coreVideoTextureCache, movieFrame, nil, GLenum(GL_TEXTURE_2D), GL_LUMINANCE, GLsizei(bufferWidth), GLsizei(bufferHeight), GLenum(GL_LUMINANCE), GLenum(GL_UNSIGNED_BYTE), 0, &luminanceGLTexture)
|
|
let luminanceGLTextureResult = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, sharedImageProcessingContext.coreVideoTextureCache, movieFrame, nil, GLenum(GL_TEXTURE_2D), GL_LUMINANCE, GLsizei(bufferWidth), GLsizei(bufferHeight), GLenum(GL_LUMINANCE), GLenum(GL_UNSIGNED_BYTE), 0, &luminanceGLTexture)
|
|
@@ -443,7 +449,7 @@ class PQMovieFilter: PQBaseFilter {
|
|
|
|
|
|
let luminanceTexture = CVOpenGLESTextureGetName(luminanceGLTexture!)
|
|
let luminanceTexture = CVOpenGLESTextureGetName(luminanceGLTexture!)
|
|
|
|
|
|
- //绑定纹理
|
|
|
|
|
|
+ // 绑定纹理
|
|
glBindTexture(GLenum(GL_TEXTURE_2D), luminanceTexture)
|
|
glBindTexture(GLenum(GL_TEXTURE_2D), luminanceTexture)
|
|
glTexParameterf(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GLfloat(GL_CLAMP_TO_EDGE))
|
|
glTexParameterf(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GLfloat(GL_CLAMP_TO_EDGE))
|
|
glTexParameterf(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GLfloat(GL_CLAMP_TO_EDGE))
|
|
glTexParameterf(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GLfloat(GL_CLAMP_TO_EDGE))
|
|
@@ -457,8 +463,7 @@ class PQMovieFilter: PQBaseFilter {
|
|
}
|
|
}
|
|
luminanceFramebuffer.timingStyle = .videoFrame(timestamp: Timestamp(withSampleTime))
|
|
luminanceFramebuffer.timingStyle = .videoFrame(timestamp: Timestamp(withSampleTime))
|
|
|
|
|
|
-
|
|
|
|
- //2 UV-plane.
|
|
|
|
|
|
+ // 2 UV-plane.
|
|
var chrominanceGLTexture: CVOpenGLESTexture?
|
|
var chrominanceGLTexture: CVOpenGLESTexture?
|
|
|
|
|
|
glActiveTexture(GLenum(GL_TEXTURE1))
|
|
glActiveTexture(GLenum(GL_TEXTURE1))
|
|
@@ -552,14 +557,13 @@ extension UIImage {
|
|
//
|
|
//
|
|
// self.init(cgImage: cgImage)
|
|
// self.init(cgImage: cgImage)
|
|
// }
|
|
// }
|
|
-
|
|
|
|
- func saveImage(currentImage: UIImage, persent: CGFloat, imageName: String){
|
|
|
|
-
|
|
|
|
- if let imageData = currentImage.jpegData(compressionQuality: persent) {
|
|
|
|
- let fullPath = NSHomeDirectory().appending("/Documents/").appending(imageName)
|
|
|
|
-
|
|
|
|
- try? imageData.write(to: URL(fileURLWithPath: fullPath))
|
|
|
|
- print("fullPath=\(fullPath)")
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+
|
|
|
|
+ func saveImage(currentImage: UIImage, persent: CGFloat, imageName: String) {
|
|
|
|
+ if let imageData = currentImage.jpegData(compressionQuality: persent) {
|
|
|
|
+ let fullPath = NSHomeDirectory().appending("/Documents/").appending(imageName)
|
|
|
|
+
|
|
|
|
+ try? imageData.write(to: URL(fileURLWithPath: fullPath))
|
|
|
|
+ print("fullPath=\(fullPath)")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|