我有一个本地视频,想把它作为纹理传递给OpenGL着色器。
我知道有许多相关主题的帖子,一些很老或很奇怪,而且有些我无法使其正常工作。
听起来应该这样做:
- 加载视频
- 获取视频输出为CVPixelBuffer
- 因此,围绕yuv vs rgb,CVOpenGLESTextureCacheCreateTextureFromImage vs glTexImage2D等方法各异。如果没有特定原因使用yuv,我宁愿坚持rgb。
我的代码能够呈现UIImages,但我无法将其调整为视频。
似乎现在建议使用CVOpenGLESTextureCacheCreateTextureFromImage而不是glTexImage2D将视频帧传递到openGL程序中。Some将视频输出缓冲区转换为图像,然后将其通过管道传递,但这听起来效率低下。
首先,以下是我如何获取视频像素缓冲区并将其传递给管理GL程序的视图(您可能可以跳过此步骤,因为我认为它可以正常工作):
import UIKit
import AVFoundation
class ViewController: UIViewController {
// video things
var videoOutput: AVPlayerItemVideoOutput!
var player: AVPlayer!
var playerItem: AVPlayerItem!
var isVideoReady = false
override func viewDidLoad() {
super.viewDidLoad()
self.setupVideo()
}
func setupVideo() -> Void {
let url = Bundle.main.urlForResource("myVideoName", withExtension: "mp4")!
let outputSettings: [String: AnyObject] = ["kCVPixelBufferPixelFormatTypeKey": Int(kCVPixelFormatType_32BGRA)]
self.videoOutput = AVPlayerItemVideoOutput.init(pixelBufferAttributes: outputSettings)
self.player = AVPlayer()
let asset = AVURLAsset(url: url)
asset.loadValuesAsynchronously(forKeys: ["playable"]) {
var error: NSError? = nil
let status = asset.statusOfValue(forKey: "playable", error: &error)
switch status {
case .loaded:
self.playerItem = AVPlayerItem(asset: asset)
self.playerItem.add(self.videoOutput)
self.player.replaceCurrentItem(with: self.playerItem)
self.isVideoReady = true
case .failed:
print("failed")
case .cancelled:
print("cancelled")
default:
print("default")
}
}
}
// this function is called just before that the openGL program renders
// and can be used to update the texture. (all the GL program is already initialized at this point)
func onGlRefresh(glView: OpenGLView) -> Void {
if self.isVideoReady {
let pixelBuffer = self.videoOutput.copyPixelBuffer(forItemTime: self.playerItem.currentTime(), itemTimeForDisplay: nil)
glView.pixelBuffer = pixelBuffer
}
}
}
这似乎工作得很好,尽管我无法真正测试它 :)
所以现在我有一个 CVPixelBuffer
(一旦视频加载完成)。如何将其传递给GL程序?
这段代码适用于一个CGImage?
// textureSource is an CGImage?
guard let textureSource = textureSource else { return }
let width: Int = textureSource.width
let height: Int = textureSource.height
let spriteData = UnsafeMutablePointer<GLubyte>(calloc(Int(UInt(CGFloat(width) * CGFloat(height) * 4)), sizeof(GLubyte.self)))
let colorSpace = textureSource.colorSpace!
let spriteContext: CGContext = CGContext(data: spriteData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width*4, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
spriteContext.draw(in: CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height)), image: textureSource)
glBindTexture(GLenum(GL_TEXTURE_2D), _textureId!)
glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), UInt32(GL_UNSIGNED_BYTE), spriteData)
free(spriteData)
但我无法理解如何有效地将其适应于 CVPixelBuffer
如果需要,我很乐意分享更多代码,但我认为这篇文章已经够长了 :)
========== 编辑 ==========
我已经查看了一堆库(都是从苹果的CameraRipple和Ray Wenderlich的教程复制而来),这里是到目前为止我的github库(我会保持它活跃以保留链接)。虽然不是理想的,但我不想在这里粘贴太多代码。我已经能够让一些视频纹理工作,但存在以下问题:
- 颜色不正确
- 模拟器中的显示与设备上的显示不同。在模拟器中,只显示视频的左半部分(并覆盖整个屏幕),并且存在一些垂直畸变。
模拟器问题似乎与XCode 8处于测试版有关,但我不确定...