Sources/Classes/View/CoreVideo/QTCVOpenGLView.m
/* |
File: QTCVOpenGLView.m |
Abstract: |
QT Core Video rendering class. |
Version: 2.0 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2013 Apple Inc. All Rights Reserved. |
*/ |
#pragma mark - |
#pragma mark Private - Headers |
#import "QTVisualContext.h" |
#import "QTCVOpenGLView.h" |
#pragma mark - |
#pragma mark Private - Data Structures |
struct CVOpenGLViewPlayback |
{ |
NSSize m_Size; // Frame size |
CFAllocatorRef mpAllocator; // CF allocator used throughout |
CGDirectDisplayID mnDisplayId; // Display used by CoreVideo |
CVDisplayLinkRef mpDisplayLink; // Display link maintained by CV |
CVOptionFlags mnLockFlags; // Flags used for locking the base address |
CVImageBufferRef mpVideoFrame; // The current frame from CV |
}; |
typedef struct CVOpenGLViewPlayback CVOpenGLViewPlayback; |
struct QTCVOpenGLViewData |
{ |
BOOL mbDisplay; |
CVOpenGLViewPlayback m_Playback; |
QTVisualContext *mpVisualContext; |
QTCVOpenGLView *mpSelf; |
CGLContextObj mpContextObj; |
NSOpenGLContext *mpContext; |
QTMovie *mpMovie; |
}; |
typedef struct QTCVOpenGLViewData QTQTCVOpenGLViewData; |
#pragma mark - |
#pragma mark Private - Utilties - Constructors |
// Constructor for CoreVideo OpenGL view |
static QTCVOpenGLViewDataRef CVOpenGLViewCreate(QTCVOpenGLView *pBase) |
{ |
QTCVOpenGLViewDataRef pView = (QTCVOpenGLViewDataRef)calloc(1, sizeof(QTQTCVOpenGLViewData)); |
if(pView != NULL) |
{ |
pView->mpSelf = pBase; |
pView->m_Playback.m_Size.width = 1920.0f; |
pView->m_Playback.m_Size.height = 1080.0f; |
pView->m_Playback.mpAllocator = kCFAllocatorDefault; |
pView->m_Playback.mnDisplayId = kCGDirectMainDisplay; |
} // if |
else |
{ |
NSLog(@">> [QTCoreVideo OpenGL View] ERROR: Failure Allocating Memory For View Attributes!"); |
} // else |
return pView; |
} // CVOpenGLViewCreate |
#pragma mark - |
#pragma mark Private - Utilties - Destructors |
// Stop and release the mpMovie |
static inline void CVOpenGLViewDeleteMovie(QTCVOpenGLViewDataRef pView) |
{ |
if(pView->mpMovie) |
{ |
[pView->mpMovie setRate:0.0]; |
SetMovieVisualContext([pView->mpMovie quickTimeMovie], NULL); |
[pView->mpMovie release]; |
pView->mpMovie = nil; |
} // if |
} // CVOpenGLViewDeleteMovie |
// It is critical to dispose of the display link |
static inline void CVOpenGLViewDeleteDisplayLink(QTCVOpenGLViewDataRef pView) |
{ |
if(pView->m_Playback.mpDisplayLink) |
{ |
CVDisplayLinkStop(pView->m_Playback.mpDisplayLink); |
CVDisplayLinkRelease(pView->m_Playback.mpDisplayLink); |
pView->m_Playback.mpDisplayLink = NULL; |
} // if |
} // CVOpenGLViewDeleteDisplayLink |
static inline void CVOpenGLViewDeleteTexture(QTCVOpenGLViewDataRef pView) |
{ |
if(pView->m_Playback.mpVideoFrame != NULL) |
{ |
CVOpenGLTextureRelease(pView->m_Playback.mpVideoFrame); |
pView->m_Playback.mpVideoFrame = NULL; |
} // if |
} // CVOpenGLViewDeleteTexture |
// Don't leak Core Video OpenGL textures |
static inline void CVOpenGLViewDeleteTextureBlocking(QTCVOpenGLViewDataRef pView) |
{ |
CGLContextObj pContextObj = (CGLContextObj)[[pView->mpSelf openGLContext] CGLContextObj]; |
if(pContextObj != NULL) |
{ |
CGLLockContext(pContextObj); |
{ |
CVOpenGLViewDeleteTexture(pView); |
} |
CGLUnlockContext(pContextObj); |
} // if |
} // CVOpenGLViewDeleteTextureBlocking |
// Release the pixel image context |
static inline void CVOpenGLViewDeleteContext(QTCVOpenGLViewDataRef pView) |
{ |
if(pView->mpVisualContext) |
{ |
[pView->mpVisualContext release]; |
pView->mpVisualContext = nil; |
} // if |
} // CVOpenGLViewDeleteContext |
// Delete the object |
static void CVOpenGLViewDelete(QTCVOpenGLViewDataRef pView) |
{ |
if(pView != NULL) |
{ |
CVOpenGLViewDeleteTextureBlocking(pView); |
CVOpenGLViewDeleteDisplayLink(pView); |
CVOpenGLViewDeleteContext(pView); |
CVOpenGLViewDeleteMovie(pView); |
free(pView); |
pView = NULL; |
} // if |
} // CVOpenGLViewDelete |
#pragma mark - |
#pragma mark Private - Utilties - Draw |
static CVReturn CVOpenGLViewDisplayBegin(QTCVOpenGLViewDataRef pView) |
{ |
// Current OpenGL context |
pView->mpContext = [pView->mpSelf openGLContext]; |
if(pView->mpContext != NULL) |
{ |
// Get the current CGL context |
pView->mpContextObj = (CGLContextObj)[pView->mpContext CGLContextObj]; |
if(pView->mpContextObj != NULL) |
{ |
// Make the GL context the current context |
CGLSetCurrentContext(pView->mpContextObj); |
// Lock the CGL context |
CGLError nError = CGLLockContext(pView->mpContextObj); |
// Update view transformations, but don't block |
[pView->mpSelf transform:NO]; |
// Clear color, stencil, and depth buffers |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); |
// Return the result |
pView->mbDisplay = nError == kCGLNoError; |
} // if |
} // if |
return pView->mbDisplay ? kCVReturnSuccess : kCVReturnInvalidDisplay; |
} // CVOpenGLViewDisplayBegin |
static void CVOpenGLViewDisplayEnd(QTCVOpenGLViewDataRef pView) |
{ |
if(pView->mbDisplay) |
{ |
// Give time to the Visual Context so it can release internally held |
// resources for later re-use this function should be called in every |
// rendering pass, after old images have been released, new images |
// have been used and all rendering has been flushed to the screen. |
[pView->mpVisualContext task]; |
// For double-buffering, perform a flush |
CGLFlushDrawable(pView->mpContextObj); |
// Unlock the CG context |
CGLUnlockContext(pView->mpContextObj); |
// Reset the display flag |
pView->mbDisplay = NO; |
} // if |
} // CVOpenGLViewDisplayEnd |
#pragma mark - |
#pragma mark Private - Callbacks - Render |
static inline BOOL CVOpenGLViewIsValidVisualContext(const CVTimeStamp *pTimeStampOut, |
QTCVOpenGLViewDataRef pView) |
{ |
return ([pView->mpVisualContext isValid] && [pView->mpVisualContext isImageAvailable:pTimeStampOut]); |
} // CVOpenGLViewIsValidVisualContext |
static inline BOOL CVOpenGLViewCopyImageForTime(const CVTimeStamp *pTimeStampOut, |
QTCVOpenGLViewDataRef pView) |
{ |
// Delete the old video frame |
CVOpenGLViewDeleteTexture(pView); |
// Get an image frame from the Visual Context, indexed by the provided time |
// Make the GL context the current context |
pView->m_Playback.mpVideoFrame = [pView->mpVisualContext copyImageForTime:pTimeStampOut]; |
return pView->m_Playback.mpVideoFrame != NULL; |
} // CVOpenGLViewCopyImageForTime |
// This is the CoreVideo DisplayLink callback notifying the application when |
// the display will need each m_Size and is called when the DisplayLink is |
// running -- in response, we call our getFrameForTime method. |
static CVReturn CVOpenGLViewGetFrameForTime(CVDisplayLinkRef pDisplayLink, |
const CVTimeStamp *pTimeStampNow, |
const CVTimeStamp *pTimeStampOut, |
CVOptionFlags nFlagsIn, |
CVOptionFlags *pFlagsOut, |
void *pDisplayLinkCtx) |
{ |
CVReturn nResult = kCVReturnError; |
QTCVOpenGLViewDataRef pView = pDisplayLinkCtx; |
if(pView != NULL) |
{ |
// There is no autorelease pool when this method is called because it will |
// be called from another thread it's important to create one or you will |
// leak objects |
NSAutoreleasePool *pool = [NSAutoreleasePool new]; |
if(pool) |
{ |
// Do we have a valid visual context? |
BOOL bSuccess = CVOpenGLViewIsValidVisualContext(pTimeStampOut, pView); |
// Check for new frame |
if(bSuccess) |
{ |
// Get an image frame from the Visual Context |
bSuccess = CVOpenGLViewCopyImageForTime(pTimeStampOut, pView); |
if(bSuccess) |
{ |
// Display video frame |
nResult = CVOpenGLViewDisplayBegin(pView); |
if(nResult == kCVReturnSuccess) |
{ |
[pView->mpSelf display]; |
} // if |
// Flush buffer and end display |
CVOpenGLViewDisplayEnd(pView); |
} // if |
else |
{ |
NSLog(@">> WARNING: QT Visual Context Copy Image for Time Error!"); |
} // else |
} // if |
[pool release]; |
} // if |
} // if |
return nResult; |
} // CVOpenGLViewGetFrameForTime |
#pragma mark - |
#pragma mark Private - Utilities - Acquire |
static BOOL CVOpenGLViewAcquireDisplayLink(QTCVOpenGLViewDataRef pView) |
{ |
CVOpenGLViewDeleteDisplayLink(pView); |
// Create display link for the main display |
CVDisplayLinkCreateWithCGDisplay(pView->m_Playback.mnDisplayId, |
&pView->m_Playback.mpDisplayLink); |
BOOL bSuccess = pView->m_Playback.mpDisplayLink != NULL; |
if(bSuccess) |
{ |
// Set the current display of a display link. |
CVDisplayLinkSetCurrentCGDisplay(pView->m_Playback.mpDisplayLink, |
pView->m_Playback.mnDisplayId); |
// Set the renderer output callback function |
CVDisplayLinkSetOutputCallback(pView->m_Playback.mpDisplayLink, |
&CVOpenGLViewGetFrameForTime, |
pView); |
// Activates a display link |
CVDisplayLinkStart(pView->m_Playback.mpDisplayLink); |
} // if |
return bSuccess; |
} // CVOpenGLViewAcquireDisplayLink |
static BOOL CVOpenGLViewAcquireVisualContext(QTCVOpenGLViewDataRef pView) |
{ |
// Delete the old qt visual context |
CVOpenGLViewDeleteContext(pView); |
// Instantiate a new qt visual context object |
pView->mpVisualContext = [[QTVisualContext alloc] initVisualContextWithSize:&pView->m_Playback.m_Size |
type:kQTVisualContextOpenGLTexture |
context:[pView->mpSelf openGLContext] |
format:[pView->mpSelf pixelFormat]]; |
BOOL bSuccess = pView->mpVisualContext != nil; |
if(bSuccess) |
{ |
// Targets a movie to render into a visual context |
[pView->mpVisualContext setMovie:pView->mpMovie]; |
} // if |
return bSuccess; |
} // CVOpenGLViewAcquireVisualContext |
static BOOL CVOpenGLViewAcquireMovieAttributes(NSString *pMoviePath, |
QTCVOpenGLViewDataRef pView) |
{ |
BOOL bSuccess = pMoviePath != nil; |
if(pMoviePath) |
{ |
// If we already have a movie release it |
CVOpenGLViewDeleteMovie(pView); |
// Instantiate a movie object |
pView->mpMovie = [[QTMovie alloc] initWithFile:pMoviePath |
error:nil]; |
// Is a valid movie |
bSuccess = pView->mpMovie != nil; |
// Get the movie size |
if(bSuccess) |
{ |
// Get the movie width & height |
[[pView->mpMovie attributeForKey:QTMovieNaturalSizeAttribute] getValue:&pView->m_Playback.m_Size]; |
// Set movie to loop |
[pView->mpMovie setAttribute:[NSNumber numberWithBool:YES] |
forKey:QTMovieLoopsAttribute]; |
// Play the Movie |
[pView->mpMovie setRate:1.0]; |
} // if |
} // if |
return bSuccess; |
} // CVOpenGLViewAcquireMovieAttributes |
// New QT & CV resources for a movie |
static inline BOOL CVOpenGLViewAcquireResourceForMovie(NSString *pMoviePath, |
QTCVOpenGLViewDataRef pView) |
{ |
BOOL bSuccess = CVOpenGLViewAcquireMovieAttributes(pMoviePath, pView); |
if(bSuccess) |
{ |
bSuccess = CVOpenGLViewAcquireDisplayLink(pView); |
bSuccess = bSuccess && CVOpenGLViewAcquireVisualContext(pView); |
} // if |
return bSuccess; |
} // CVOpenGLViewAcquireResourceForMovie |
// Open a Movie File and instantiate a QTMovie object |
static inline BOOL CVOpenGLViewOpenMovie(NSString *pMoviePath, |
QTCVOpenGLViewDataRef pView) |
{ |
BOOL bSuccess = CVOpenGLViewAcquireResourceForMovie(pMoviePath, pView); |
// New movie resources |
if(bSuccess) |
{ |
// Set the window title from the Movie if it has a name associated with it |
[[pView->mpSelf window] setTitle:[pView->mpMovie attributeForKey:QTMovieDisplayNameAttribute]]; |
} // if |
return bSuccess; |
} // CVOpenGLViewOpenMovie |
#pragma mark - |
#pragma mark Private - Utilties - Display Link |
static inline BOOL CVOpenGLViewStartDisplayLink(QTCVOpenGLViewDataRef pView) |
{ |
BOOL bSuccess = pView->m_Playback.mpDisplayLink != NULL; |
if(bSuccess) |
{ |
bSuccess = !CVDisplayLinkIsRunning(pView->m_Playback.mpDisplayLink); |
if(bSuccess) |
{ |
CVDisplayLinkStart(pView->m_Playback.mpDisplayLink); |
} // if |
} // if |
return bSuccess; |
} // CVOpenGLViewStartDisplayLink |
static inline BOOL CVOpenGLViewStopDisplayLink(QTCVOpenGLViewDataRef pView) |
{ |
BOOL bSuccess = pView->m_Playback.mpDisplayLink != NULL; |
if(bSuccess) |
{ |
bSuccess = CVDisplayLinkIsRunning(pView->m_Playback.mpDisplayLink); |
if(bSuccess) |
{ |
CVDisplayLinkStop(pView->m_Playback.mpDisplayLink); |
} // if |
} // if |
return bSuccess; |
} // CVOpenGLViewStopDisplayLink |
#pragma mark - |
@implementation QTCVOpenGLView |
#pragma mark - |
#pragma mark Public - Awake From Nib |
- (id) initWithFrame:(NSRect)frame |
{ |
self = [super initWithFrame:frame]; |
if(self) |
{ |
mpView = CVOpenGLViewCreate(self); |
} // if |
return self; |
} // initWithFrame |
#pragma mark - |
#pragma mark Public - Destructors |
// It is very important that we clean up the rendering objects before the |
// view is disposed, remember that with the display link running you're |
// applications render callback may be called at any time including when |
// the application is quitting or the view is being disposed, additionally |
// you need to make sure you're not consuming OpenGL resources or leaking |
// textures -- this clean up routine makes sure to stop and release |
// everything. |
- (void) cleanUp |
{ |
CVOpenGLViewDelete(mpView); |
[super cleanUp]; |
} // cleanUp |
- (void) dealloc |
{ |
CVOpenGLViewDelete(mpView); |
[super dealloc]; |
} // dealloc |
#pragma mark - |
#pragma mark Public - Accessors |
// Video pixel buffer |
- (CVPixelBufferRef) buffer |
{ |
return mpView->m_Playback.mpVideoFrame; |
} // buffer |
// Video frame size |
- (NSSize) size |
{ |
return mpView->m_Playback.m_Size; |
} // frame |
#pragma mark - |
#pragma mark Public - Utilities - Display Link |
// Start display link |
- (BOOL) start |
{ |
return CVOpenGLViewStartDisplayLink(mpView); |
} // start |
// Stop display link |
- (BOOL) stop |
{ |
return CVOpenGLViewStopDisplayLink(mpView); |
} // start |
// Display the video |
- (void) display |
{ |
return; |
} // display |
#pragma mark - |
#pragma mark Public - Utilities - Movie |
// Open a Movie File and instantiate a QTMovie object |
- (BOOL) openMovie:(NSString *)theMoviePath |
{ |
return CVOpenGLViewOpenMovie(theMoviePath, mpView); |
} // openMovie |
@end |
Copyright © 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-07-18