渲染 QCRenderer 以显示到 DeckLink 卡上

Rendering QCRenderer for displaying onto DeckLink Cards

我正在 xcode 在 MacMini(2012 年末)上编写应用程序。

这是一个应用程序,我可以在其中加载一些 QuartzComposer 文件(使用 QCRenderer class)。然后将这些文件渲染到视频内存中,并使用 glReadPixels 将它们读回以获取所有像素数据。然后将此像素数据推送到 decklink 帧(我使用的是 BlackMagic Decklink SDK),以便在 DeckLink Quad 上播放。一切都很好。甚至可以在 HD (1080i50) 上渲染 3 个输出而不会丢帧。但是过了一会儿(比如 5 分钟),即使我只渲染 1 个输出,它也会丢帧。

所以我认为有两个可能的原因。第一的。当有一个完整的帧(帧播放完)回调时,我从 SDK 收到 bmdOutputFrameDisplayedLate,这意味着帧没有在预定的时间播放。因此,当发生这种情况时,我会将下一帧推到未来。

其次。我已经设置了一个 frameBuffer 大小(在开始播放之前渲染出 3 帧)。因此,也许一段时间后渲染落后于调度,这将导致 dropping/late 帧。也许我没有像它应该的那样进行 openGL 渲染过程?

所以这是我的代码:

-> 首先我将 QCRenderer 加载到内存中

- (id)initWithPath:(NSString*)path forResolution:(NSSize)resolution
{
    if (self = [super init])
    {        
        NSOpenGLPixelFormatAttribute    attributes[] = {
            NSOpenGLPFAPixelBuffer,
            NSOpenGLPFANoRecovery,
            NSOpenGLPFAAccelerated,
            NSOpenGLPFADepthSize, 24,
            (NSOpenGLPixelFormatAttribute) 0
        };

        NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];

        quartzPixelBuffer = nil;

        quartzPixelBuffer = [[NSOpenGLPixelBuffer alloc] initWithTextureTarget:GL_TEXTURE_2D textureInternalFormat:GL_RGBA textureMaxMipMapLevel:0 pixelsWide:resolution.width pixelsHigh:resolution.height];

        if(quartzPixelBuffer == nil)
        {
            NSLog(@"Cannot create OpenGL pixel buffer");
        }

        //Create the OpenGL context to render with (with color and depth buffers)
        quartzOpenGLContext = [[NSOpenGLContext alloc] initWithFormat:format shareContext:nil];
        if(quartzOpenGLContext == nil)
        {
            NSLog(@"Cannot create OpenGL context");
        }

        [quartzOpenGLContext setPixelBuffer:quartzPixelBuffer cubeMapFace:0 mipMapLevel:0 currentVirtualScreen:[quartzOpenGLContext currentVirtualScreen]];

        //Create the QuartzComposer Renderer with that OpenGL context and the specified composition file
        NSString* correctPath = [path substringWithRange:NSMakeRange(0, path.length - 1)];

        quartzRenderer = [[QCRenderer alloc] initWithOpenGLContext:quartzOpenGLContext pixelFormat:format file:correctPath];

        if(quartzRenderer == nil)
        {
            NSLog(@"Cannot create QCRenderer");
        }
    }
    return self;
}

-> 下一步是在开始播放之前渲染 3 帧(BUFFER_DEPTH 当前设置为 3)

- (void) preRollFrames;
{
    // reset scheduled
    [self resetScheduled];
    totalFramesScheduled = 0;

    if (isRunning == TRUE)
    {
        [self stopPlayback];
    }

    @autoreleasepool
    {
        for (double i = 0.0; i < ((1.0 / framesPerSecond) * BUFFER_DEPTH); i += 1.0/framesPerSecond)
        {
            // render image at given time
            [self createVideoFrame:TRUE];
        }
    }
}

-> 这是 createVideoFrame 函数。当 scheduleBlack 设置为 true 时,必须呈现黑色帧。如果为 false,则调用 QCRenderer class 的函数 renderFrameAtTime。然后将此函数的 return 传递给 decklinkVideoFrame 对象。接下来,此帧将被推入 DeckLink 卡 (SDK) 的调度队列。

- (void) createVideoFrame:(BOOL)schedule
{
    @autoreleasepool
    {
        // get displaymode
        IDeckLinkDisplayMode* decklinkdisplaymode = (IDeckLinkDisplayMode*)CurrentRes;

        // create new videoframe on output
        if (deckLinkOutput->CreateVideoFrame((int)decklinkdisplaymode->GetWidth(), (int)decklinkdisplaymode->GetHeight(), (int)decklinkdisplaymode->GetWidth() * 4, bmdFormat8BitARGB, bmdFrameFlagFlipVertical, &videoFrame) != S_OK)
        {
            // failed to create new video frame on output
            // display terminal message
            sendMessageToTerminal = [[mogiTerminalMessage alloc] initWithSendNotification:@"terminalErrorMessage" forMessage:[NSString stringWithFormat:@"DeckLink: Output %d -> Failed to create new videoframe", outputID]];
        }

        unsigned frameBufferRowBytes = ((int)decklinkdisplaymode->GetWidth() * 4 + 63) & ~63;
        void* frameBufferPtr = valloc((int)decklinkdisplaymode->GetHeight() * frameBufferRowBytes);

        // set videoframe pointer
        if (videoFrame != NULL)
        {
            videoFrame->GetBytes((void**)&frameBufferPtr);
        }

        // fill pointer with pixel data
        if (scheduleBlack == TRUE)
        {
            [qClear renderFrameAtTime:1.0 forBuffer:(void**)frameBufferPtr forScreen:0];

            // render first frame qRenderer
            if (qRender != NULL)
            {
                [qRender renderFirstFrame];
            }
        }
        else
        {
            [qRender renderFrameAtTime:totalSecondsScheduled forBuffer:(void**)frameBufferPtr forScreen:screenID];
            schedule = TRUE;
        }

        // if playback -> schedule frame
        if (schedule == TRUE)
        {
            // schedule frame
            if (videoFrame != NULL)
            {
                if (deckLinkOutput->ScheduleVideoFrame(videoFrame, (totalFramesScheduled * frameDuration), frameDuration, frameTimescale) != S_OK)
                {
                    // failed to schedule new frame
                    // display message to terminal
                    sendMessageToTerminal = [[mogiTerminalMessage alloc] initWithSendNotification:@"terminalErrorMessage" forMessage:[NSString stringWithFormat:@"DeckLink: Output %d -> Failed to schedule new videoframe", outputID]];
                }
                else
                {
                    // increase totalFramesScheduled
                    totalFramesScheduled ++;

                    // increase totalSecondsScheduled
                    totalSecondsScheduled += 1.0/framesPerSecond;
                }

                // clear videoframe
                videoFrame->Release();
                videoFrame = NULL;
            }
        }
    }
}

-> 从 QCRenderer 渲染 frameAtTime 函数 class

- (void) renderFrameAtTime:(double)time forBuffer:(void*)frameBuffer forScreen:(int)screen
{
    @autoreleasepool
    {
        CGLContextObj cgl_ctx = [quartzOpenGLContext CGLContextObj];

        // render frame at time
        [quartzRenderer renderAtTime:time arguments:NULL];

        glReadPixels(0, 0, [quartzPixelBuffer pixelsWide], [quartzPixelBuffer pixelsHigh], GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, frameBuffer);
    }
}

-> 滚动帧后开始播放。每次都有一帧播放出来。此回调方法称为(Decklink SDK)。如果有延迟帧。我将 totalFrames 1 帧推到未来。

PlaybackDelegate::PlaybackDelegate (DecklinkDevice* owner)
{
    pDecklinkDevice = owner;
}

HRESULT     PlaybackDelegate::ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result)
{
    if (result == bmdOutputFrameDisplayedLate)
    {
        // if displayed late bump scheduled time further into the future by one frame
        [pDecklinkDevice increaseScheduledFrames];
        NSLog(@"bumped %d", [pDecklinkDevice getOutputID]);
    }

    if ([pDecklinkDevice getIsRunning] == TRUE)
    {
        [pDecklinkDevice createVideoFrame:TRUE];
    }

    return S_OK;
}

所以我的问题。我的 openGL 渲染过程是否正确?也许这会导致几分钟后的延迟。或者我正在处理不正确的 displayedLate 帧,所以调度队列的时间在一段时间后被弄乱了?

谢谢! 托马斯

迟到时,尝试根据ScheduledFrameCompleted回调指定的完成结果推进帧计数器。考虑额外增加两个。

至少在 Windows 并且多年来,只有工作站主板在使用 NVidia 的产品时提供不受限制的像素回读。我的 iMac 有一块 GeForce 系列卡,但我没有测量过它的性能。如果 glReadPixels 受到限制,我不会感到惊讶。

也尝试使用 GL_BGRA_EXT 和 GL_UNSIGNED_INT_8_8_8_8_REV。

您应该具有 glReadPixels 和硬件写入的精确计时指标。我假设您正在回读逐行或隔行扫描的帧,而不是场。理想情况下,像素回读应小于 10 毫秒。显然,整个渲染周期需要比视频硬件的帧速率更快。