从AVPlayerItemVideoOutput到openGL纹理的最佳路径

时间:2014-07-24 12:16:40

标签: macos opengl video avfoundation avplayer

一直在试图找出从AVFoundation视频到openGLTexture的当前最佳路径,我找到的大部分内容都与iOS有关,而且我似乎无法在OSX中运行良好。< / p>

首先,这是我设置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中的实现看起来略有不同,我无法让它呈现除黑色之外的任何东西,而且它似乎比第一个解决方案更慢...... / p>

- (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 Textures的大多数苹果示例与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中具有不同的实现。

所以任何指针都会很棒,它看起来像是重要的功能,但是我发现有关avx基础和osx上的opengl的信息似乎非常消极。

更新:使用计数更新了ioSurface代码,工作时间稍长,但最终仍然有问题。

2 个答案:

答案 0 :(得分:0)

使用选项#2时,您是否设置了&#34;保留支持&#34; EAGLLayer的财产是否属于?这可能就是为什么它似乎在不同的周期中使用相同的帧。看看您如何为要渲染帧缓冲区的视图正确配置图层或者是否配置图层将是有益的...

答案 1 :(得分:0)

OSX上的快速视频纹理的相应方法似乎是使用BiPlanar IOSurfaces,其中surfacePlane [0]是亮度(Y),surfacePlane [1]是二次采样色度(UV)。我的代码在Core Profile上运行,因此CGLTexImageIOSurface2D的GLenum常量反映相同。很确定只支持矩形纹理。我使用GLSL着色器来组合它们,它在Sierra上运行得很好。简要总结:

NSDictionary* pixelBufferAttributesIOSurfaceBiPlanar = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:kPixelFormatTypeGL], ARC_BRIDGE(id)kCVPixelBufferPixelFormatTypeKey,
[NSNumber numberWithBool:YES], ARC_BRIDGE(id)kCVPixelBufferOpenGLCompatibilityKey,
[NSNumber numberWithBool:YES], ARC_BRIDGE(id)kCVPixelBufferIOSurfaceOpenGLTextureCompatibilityKey,
[NSDictionary dictionary], ARC_BRIDGE(id)kCVPixelBufferIOSurfacePropertiesKey,
nil];

/* luma texture */
CGLTexImageIOSurface2D(glContext,GL_TEXTURE_RECTANGLE,GL_R8,(GLsizei)textureSize.width,(GLsizei)textureSize.height,GL_RED,GL_UNSIGNED_BYTE,surfaceRef,0);
/* chroma texture */
CGLTexImageIOSurface2D(glContext,GL_TEXTURE_RECTANGLE,GL_RG8,(GLsizei)textureSize.width,(GLsizei)textureSize.height,GL_RG,GL_UNSIGNED_BYTE,surfaceRef,1);

GLSL

uniform sampler2DRect textureSampler0;
uniform sampler2DRect textureSampler1;
// ...
vec3 YCrCb;
vec2 lumaCoords = texCoord0;
vec2 chromaCoords = lumaCoords*vec2(0.5,0.5);
vec2 chroma = texture(textureSampler1,chromaCoords).xy;
float luma = texture(textureSampler0,lumaCoords).x;
YCrCb.x = (luma-(16.0/255.0)); // video range
YCrCb.yz = (chroma-vec2(0.5,0.5));
vec4 rgbA = vec4(colorConversionMatrix * YCrCb,1.0);

应从CVPixelBufferRef

生成颜色转换矩阵

CFTypeRef colorAttachments = CVBufferGetAttachment(pixelBuffer,kCVImageBufferYCbCrMatrixKey,NULL);

IOSurfaceRef是一个CFTypeRef派生对象,我使用CFRetain / CFRelease来保持表面,直到我不需要纹理。如果CGLTexImageIOSurface2D执行GPU blit上传纹理数据,可能只需要围绕调用CGLTexImageIOSurface2D进行CFRetain / Release。

我开始使用Apple iOS示例代码AVBasicVideoOutput。我把它移植到Metal for iOS&amp; OSX在两天内然后花了一周的时间试图找出OpenGL Core Profile版本。如果可以,请使用Metal:它更快,并且iOS / OSX上的代码几乎完全相同。此外,在WWDC13会话507中有一些很好的信息 OpenGL for OS X的新功能。特别是,有一个GLSL着色器兼容性标志,允许EAGL着色器在OSX上大部分未经修改地运行。