我一直在苦恼如何从AVFoundation视频转换成OpenGL纹理,大部分的资料都和iOS相关,而且在OSX系统中无法正常工作。
首先,这是我设置videoOutput的方法:
NSDictionary *pbOptions = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kCVPixelFormatType_422YpCbCr8], kCVPixelBufferPixelFormatTypeKey,
[NSDictionary dictionary], kCVPixelBufferIOSurfacePropertiesKey,
nil];
self.playeroutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pbOptions];
self.playeroutput.suppressesPlayerRendering = YES;
我将尝试三种不同的解决方案,其中只有一种似乎能够始终正常工作,但我不确定它是否是最快的。其中一种可以在一段时间内工作,然后会出现框架跳动的问题,而另一种则只会产生黑色。
首先,使用glTexImage2D的有效解决方案。
- (BOOL) renderWithCVPixelBufferForTime: (NSTimeInterval) time
{
CMTime vTime = [self.playeroutput itemTimeForHostTime:CACurrentMediaTime()];
if ([self.playeroutput hasNewPixelBufferForItemTime:vTime]) {
if (_cvPixelBufferRef) {
CVPixelBufferUnlockBaseAddress(_cvPixelBufferRef, kCVPixelBufferLock_ReadOnly);
CVPixelBufferRelease(_cvPixelBufferRef);
}
_cvPixelBufferRef = [self.playeroutput copyPixelBufferForItemTime:vTime itemTimeForDisplay:NULL];
CVPixelBufferLockBaseAddress(_cvPixelBufferRef, kCVPixelBufferLock_ReadOnly);
GLsizei texWidth = CVPixelBufferGetWidth(_cvPixelBufferRef);
GLsizei texHeight = CVPixelBufferGetHeight(_cvPixelBufferRef);
GLvoid *baseAddress = CVPixelBufferGetBaseAddress(_cvPixelBufferRef);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textureName);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_STORAGE_HINT_APPLE , GL_STORAGE_CACHED_APPLE);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGB, texWidth, texHeight, 0, GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, baseAddress);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
}
return YES;
}
这种方法大部分时间都在锁定像素缓冲区的基地址,但文档说如果从GPU访问数据,则不需要锁定,否则可能会影响性能。我无法找到一种不用锁定就可以获取纹理的方法。
接下来是几乎可行的解决方案,使用iOSurface,这个方法一开始可以工作,但随后出现了严重的故障,好像之前帧中使用了ioSurfaces:
- (BOOL) renderWithIOSurfaceForTime:(NSTimeInterval) time {
CMTime vTime = [self.playeroutput itemTimeForHostTime:CACurrentMediaTime()];
if ([self.playeroutput hasNewPixelBufferForItemTime:vTime]) {
CVPixelBufferRef pb = [self.playeroutput copyPixelBufferForItemTime:vTime itemTimeForDisplay:NULL];
IOSurfaceRef newSurface = CVPixelBufferGetIOSurface(pb);
if (_surfaceRef != newSurface) {
IOSurfaceDecrementUseCount(_surfaceRef);
_surfaceRef = newSurface;
IOSurfaceIncrementUseCount(_surfaceRef);
GLsizei texWidth = (int) IOSurfaceGetWidth(_surfaceRef);
GLsizei texHeight= (int) IOSurfaceGetHeight(_surfaceRef);
size_t rowbytes = CVPixelBufferGetBytesPerRow(_cvPixelBufferRef);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textureName);
CGLTexImageIOSurface2D(cgl_ctx, GL_TEXTURE_RECTANGLE_ARB, GL_RGB8, texWidth, texHeight, GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, _surfaceRef, 0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
}
CVPixelBufferRelease(pb);
}
return YES;
}
如果能够奏效,这似乎是最佳解决方案。我有另一个从ioSurfaces创建纹理的过程,它工作得非常好,而且速度也非常快。
最后,在iOS上似乎推荐使用CVOpenGLTextureCache,osx上的实现略有不同,我无法使其渲染除黑色以外的任何内容,而且它似乎比第一种解决方案还要慢....
- (BOOL) renderByCVOpenGLTextureCacheForTime:(NSTimeInterval) time
{
CMTime vTime = [self.playeroutput itemTimeForHostTime:CACurrentMediaTime()];
if ([self.playeroutput hasNewPixelBufferForItemTime:vTime]) {
_cvPixelBufferRef = [self.playeroutput copyPixelBufferForItemTime:vTime itemTimeForDisplay:NULL];
if (!_textureCacheRef) {
CVReturn error = CVOpenGLTextureCacheCreate(kCFAllocatorDefault, NULL, cgl_ctx, CGLGetPixelFormat(cgl_ctx), NULL, &_textureCacheRef);
if (error) {
NSLog(@"Texture cache create failed");
}
}
CVReturn error = CVOpenGLTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCacheRef, _cvPixelBufferRef, NULL, &_textureRef);
if (error) {
NSLog(@"Failed to copy video texture");
}
CVPixelBufferRelease(_cvPixelBufferRef);
_textureName = CVOpenGLTextureGetName(_textureRef);
}
return YES;
}
也许我没有正确设置东西,在osx中纹理缓存没有任何文档。
在渲染周期之间保留cvpixelbufferref是最好的,因为据我了解,纹理上传可以与CGLTexImage2d异步运行,我非常满意这一点,几个其他对象可以同时呈现,当纹理最终被绘制时,最终会调用cglflushDrawable。
我发现大多数苹果示例关于视频到OpenGL纹理都涉及iOS,并将纹理分成两部分以在着色器中重新组合,就像在这个例子中 https://developer.apple.com/library/ios/samplecode/GLCameraRipple/Listings/GLCameraRipple_main_m.html#//apple_ref/doc/uid/DTS40011222-GLCameraRipple_main_m-DontLinkElementID_11 我无法直接适应代码,因为iOS中的纹理缓存具有不同的实现。
所以任何指针都很好,它似乎是至关重要的功能,但我发现有关av foundation和opengl在osx上的信息非常消极。
更新:使用计数更新ioSurface代码,工作时间稍长,但最终仍会出现故障。