渲染 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 毫秒。显然,整个渲染周期需要比视频硬件的帧速率更快。
我正在 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 毫秒。显然,整个渲染周期需要比视频硬件的帧速率更快。