在iOS中将OpenGL ES渲染到视频中(使用iOS 5纹理缓存将其渲染到纹理上)。

4
你知道苹果的 CameraRipple 特效的示例代码吗?我正在尝试在OpenGL完成所有水波纹特效后将相机输出记录到文件中。我已经使用了 glReadPixels,其中我读取了 void * 缓冲区中的所有像素,创建了 CVPixelBufferRef 并将其附加到 AVAssetWriterInputPixelBufferAdaptor 中,但速度太慢,因为 readPixels 需要大量时间。我发现使用 FBO 和纹理缓存可以做到同样的效果,但更快。这是我在 Apple 使用的 drawInRect 方法中的代码:
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge void *)_context, NULL, &coreVideoTextureCashe);
if (err) 
{
    NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreate %d");
}


CFDictionaryRef empty; // empty value for attr value.
CFMutableDictionaryRef attrs2;
empty = CFDictionaryCreate(kCFAllocatorDefault, // our empty IOSurface properties dictionary
                           NULL,
                           NULL,
                           0,
                           &kCFTypeDictionaryKeyCallBacks,
                           &kCFTypeDictionaryValueCallBacks);
attrs2 = CFDictionaryCreateMutable(kCFAllocatorDefault,
                                  1,
                                  &kCFTypeDictionaryKeyCallBacks,
                                  &kCFTypeDictionaryValueCallBacks);

CFDictionarySetValue(attrs2,
                     kCVPixelBufferIOSurfacePropertiesKey,
                     empty);

//CVPixelBufferPoolCreatePixelBuffer (NULL, [assetWriterPixelBufferInput pixelBufferPool], &renderTarget);
CVPixelBufferRef pixiel_bufer4e = NULL;

CVPixelBufferCreate(kCFAllocatorDefault, 
                    (int)_screenWidth, 
                    (int)_screenHeight,
                    kCVPixelFormatType_32BGRA,
                    attrs2,
                    &pixiel_bufer4e);
CVOpenGLESTextureRef renderTexture;
CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault,
                                              coreVideoTextureCashe, pixiel_bufer4e,
                                              NULL, // texture attributes
                                              GL_TEXTURE_2D,
                                              GL_RGBA, // opengl format
                                              (int)_screenWidth, 
                                              (int)_screenHeight,
                                              GL_BGRA, // native iOS format
                                              GL_UNSIGNED_BYTE,
                                              0,
                                              &renderTexture);
CFRelease(attrs2);
CFRelease(empty);
glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0);

CVPixelBufferLockBaseAddress(pixiel_bufer4e, 0);

if([pixelAdapter appendPixelBuffer:pixiel_bufer4e withPresentationTime:currentTime]) {
                float result = currentTime.value;
            NSLog(@"\n\n\4eta danni i current time e : %f \n\n",result);
                currentTime = CMTimeAdd(currentTime, frameLength);
        }

CVPixelBufferUnlockBaseAddress(pixiel_bufer4e, 0);
CVPixelBufferRelease(pixiel_bufer4e);
CFRelease(renderTexture);
CFRelease(coreVideoTextureCashe);

它记录了一个视频,速度很快,但是视频只是黑色的,我想纹理缓存引用可能不正确,或者我填写错误。
作为更新,这是另一种我尝试过的方法。我肯定是漏掉了什么。在viewDidLoad中,在我设置openGL上下文之后,我这样做:
CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge   void *)_context, NULL, &coreVideoTextureCashe);

    if (err) 
    {
        NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreate %d");
    }

    //creats the pixel buffer

    pixel_buffer = NULL;
    CVPixelBufferPoolCreatePixelBuffer (NULL, [pixelAdapter pixelBufferPool], &pixel_buffer);

    CVOpenGLESTextureRef renderTexture;
    CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, coreVideoTextureCashe, pixel_buffer,
                                                  NULL, // texture attributes
                                                  GL_TEXTURE_2D,
                                                  GL_RGBA, //  opengl format
                                                   (int)screenWidth,
                                                  (int)screenHeight,
                                                  GL_BGRA, // native iOS format
                                                  GL_UNSIGNED_BYTE,
                                                  0,
                                                  &renderTexture);

    glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0);

然后在 drawInRect: 中我这样做:
 if(isRecording&&writerInput.readyForMoreMediaData) {
    CVPixelBufferLockBaseAddress(pixel_buffer, 0);

    if([pixelAdapter appendPixelBuffer:pixel_buffer withPresentationTime:currentTime]) {
        currentTime = CMTimeAdd(currentTime, frameLength);
    }
    CVPixelBufferLockBaseAddress(pixel_buffer, 0);
    CVPixelBufferRelease(pixel_buffer);
}

然而,它在renderTexture上崩溃,该变量不为空但值为0x000000001。

更新

使用下面的代码,我实际上成功获取了视频文件,但是出现了一些绿色和红色的闪烁。我使用BGRA pixelFormatType。

这里创建了纹理缓存:

CVReturn err2 = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge void *)_context, NULL, &coreVideoTextureCashe);
if (err2) 
{
    NSLog(@"Error at CVOpenGLESTextureCacheCreate %d", err);
    return;
}

然后在drawInRect中,我调用了这个:

if(isRecording&&writerInput.readyForMoreMediaData) {
    [self cleanUpTextures];



    CFDictionaryRef empty; // empty value for attr value.
    CFMutableDictionaryRef attrs2;
    empty = CFDictionaryCreate(kCFAllocatorDefault, // our empty IOSurface properties dictionary
                           NULL,
                           NULL,
                           0,
                           &kCFTypeDictionaryKeyCallBacks,
                           &kCFTypeDictionaryValueCallBacks);
    attrs2 = CFDictionaryCreateMutable(kCFAllocatorDefault,
                                   1,
                                   &kCFTypeDictionaryKeyCallBacks,
                                   &kCFTypeDictionaryValueCallBacks);

    CFDictionarySetValue(attrs2,
                     kCVPixelBufferIOSurfacePropertiesKey,
                     empty);

//CVPixelBufferPoolCreatePixelBuffer (NULL, [assetWriterPixelBufferInput pixelBufferPool], &renderTarget);
    CVPixelBufferRef pixiel_bufer4e = NULL;

    CVPixelBufferCreate(kCFAllocatorDefault, 
                    (int)_screenWidth, 
                    (int)_screenHeight,
                    kCVPixelFormatType_32BGRA,
                    attrs2,
                    &pixiel_bufer4e);
    CVOpenGLESTextureRef renderTexture;
    CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault,
                                              coreVideoTextureCashe, pixiel_bufer4e,
                                              NULL, // texture attributes
                                              GL_TEXTURE_2D,
                                              GL_RGBA, // opengl format
                                              (int)_screenWidth, 
                                              (int)_screenHeight,
                                              GL_BGRA, // native iOS format
                                              GL_UNSIGNED_BYTE,
                                              0,
                                              &renderTexture);
    CFRelease(attrs2);
    CFRelease(empty);
    glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0);

    CVPixelBufferLockBaseAddress(pixiel_bufer4e, 0);

    if([pixelAdapter appendPixelBuffer:pixiel_bufer4e withPresentationTime:currentTime]) {
        float result = currentTime.value;
        NSLog(@"\n\n\4eta danni i current time e : %f \n\n",result);
        currentTime = CMTimeAdd(currentTime, frameLength);
    }

    CVPixelBufferUnlockBaseAddress(pixiel_bufer4e, 0);
    CVPixelBufferRelease(pixiel_bufer4e);
    CFRelease(renderTexture);
  //  CFRelease(coreVideoTextureCashe);
}

我知道我可以通过不在这里执行所有这些操作来大大优化它,但是我想让它能够工作。在cleanUpTextures中,我使用以下代码清除textureCache:

 CVOpenGLESTextureCacheFlush(coreVideoTextureCashe, 0);

有些可能是RGBA的问题,或者我不知道,但似乎它仍然得到了一种错误的缓存。

请提供您收到的内存警告或崩溃的截图。 - Dayan
当我点击录制按钮时,它调用了startWriting函数来启动AssetWriter,但是它会在一秒钟内冻结并出现“收到内存警告”的提示。 - user1562826
您能否使用此方法录制视频并同时将内容显示在屏幕上? - Gaurav Singh
1个回答

4
记录视频时,这不是我要使用的方法。您正在为每个渲染帧创建一个新的像素缓冲区,这会很慢,并且您从未释放它,因此您收到内存警告也就不足为奇了。
相反,请按照我在此答案中所述的做法进行操作。我为缓存纹理创建一个像素缓冲区,将该纹理分配给我正在渲染的FBO,然后使用AVAssetWriter的像素缓冲区输入在每个帧上添加该像素缓冲区。使用单个像素缓冲区要比每帧重新创建一个更快。您还需要使像素缓冲区与FBO的纹理目标相关联,而不是在每一帧上关联它。
我在我的开源GPUImage框架中将此记录代码封装在GPUImageMovieWriter中,如果您想查看此工作方式,请参阅上面链接的答案。正如我在上面链接的答案中所指出的那样,以这种方式进行录制可导致非常快速的编码。

好的,这是我在drawInRect中所做的事情,它很快,但记录的视频是黑色的。我认为textureCasheRef获取的是空的或不正确的,不知道。我更新了问题。 - user1562826
@user1562826 - 今后,您可以随时使用新信息更新原始问题。 我已经为您这样做了。 您是否正在尝试在不同于渲染的线程上访问此像素缓冲区及其绑定纹理?从多个线程同时访问OpenGL ES上下文可能会导致崩溃。 - Brad Larson
不,我只使用一个线程。我成功获取了视频文件,但我仍然认为纹理缓存没有从OpenGL上下文中正确提取,因为在视频中有这条线从屏幕的两个角落延伸出来,线的上方有点红色,下方有点绿色,并且还有一些奇怪的闪光。我更新了我的问题。感谢您的帮助! - user1562826
@BradLarson "我只是为缓存纹理创建了一个像素缓冲区" - 这是如何工作的?为什么苹果在他们的示例代码中每一帧都要创建和销毁它?对我来说,这似乎非常低效,但由于苹果没有任何文档,我看不到任何替代方案。我想要理解这个简单的情况,但我一直碰到你的答案(你所做的工作令人印象深刻!)相互交叉引用,并说“这是glReadPixels的替代品”,但没有解释其他用途。 - Adam
@Adam - 我不确定你指的是哪个苹果示例代码,但在我写上面链接答案(以及那里的代码)时,他们当时没有任何这样的示例。对于纹理缓存,设置像素缓冲区存在开销,这就是为什么您只需要设置一次,但此后内部字节直接映射到您的纹理。然后,当录制时,您希望重复使用像素缓冲区以获得最佳性能,因为其中的纹理字节将随着纹理内容的更新而更新。 - Brad Larson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接