网上搜索的大部分资料都是OpenGL ES渲染视频的,OpenGL渲染yuv数据的资料比较难找,因此编辑本文mark下;
结合网上搜索的资料,实现了在MacOS App开发中,将接收到的yuv420视频数据渲染到视图上;
本文并非原创,只是在其他作者的基础上修修改改,实现了在MacOS App开发中的,使用OpenGL渲染yuv420视频数据;
参考资料:
1.利用Qt + OpenGL 渲染 YUV数据,播放视频 mac版
2.最简单的视音频播放示例6:OpenGL播放YUV420P(通过Texture,使用Shader)
感谢第一篇资料作者jake2012,对于不同版本OpenGL的shader编写使用有些不同的有价值的分享;
顶点着色器--Shader3.vs:
#version 410
in vec4 vertexIn;
in vec2 textureIn;
out vec2 textureOut;
void main(void)
{
gl_Position = vertexIn;
textureOut = textureIn;
}
片段着色器--Shader3.frag:
#version 410
in vec2 textureOut;
out vec4 fragColor;
uniform sampler2D tex_y;
uniform sampler2D tex_u;
uniform sampler2D tex_v;
void main(void)
{
vec3 yuv;
vec3 rgb;
yuv.x = texture(tex_y, textureOut).r;
yuv.y = texture(tex_u, textureOut).r - 0.5;
yuv.z = texture(tex_v, textureOut).r - 0.5;
rgb = mat3( 1, 1, 1,
0, -0.21482, 2.12798,
1.28033, -0.38059, 0) * yuv;
fragColor = vec4(rgb, 1);
}
OpenGLRenderer.h
#import
#include "glUtil.h"
#import
@interface OpenGLRenderer : NSObject
@property (nonatomic) GLuint defaultFBOName;
- (instancetype)initWithDefaultFBO:(id)asbs;
- (void) resizeWithWidth:(GLuint)width AndHeight:(GLuint)height;
- (void) render;
- (void) dealloc;
- (void)setImage:(CVImageBufferRef)pixelBuffer;
- (void)presentYUVData:(NSData*)yuvdata width:(GLuint)width height:(GLuint)height;
@end
OpenGLRenderer.mm
#import "OpenGLRenderer.h"
#include "glUtil.h"
#include "imageUtil.h"
#include "sourceUtil.h"
#ifndef NULL
#define NULL 0
#endif
@interface OpenGLRenderer ()
{
GLuint m_program;
GLuint m_vertexBuffer;
GLuint textureUniformY;
GLuint textureUniformU;
GLuint textureUniformV;
GLuint vertexBuffer;
GLuint vertextAttribute;
GLuint textureAttribute;
GLuint id_y;
GLuint id_u;
GLuint id_v;
int m_nVideoW;
int m_nVideoH;
int m_nViewW;
int m_nViewH;
unsigned char* m_pBufYuv420p;
unsigned char* m_pBuffer;
}
@end
@implementation OpenGLRenderer
- (void)setImage:(CVImageBufferRef)pixelBuffer {
//上锁
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
{
//宽高
GLuint width = (GLuint)CVPixelBufferGetWidth(pixelBuffer);
GLuint height = (GLuint)CVPixelBufferGetHeight(pixelBuffer);
//调整宽高
[self resizeWithWidth:width AndHeight:height];
//yuv420视频数据
m_pBufYuv420p = NULL;
m_pBufYuv420p = (unsigned char*)CVPixelBufferGetBaseAddress(pixelBuffer);
//渲染yuv420
[self rendeYUVData:m_pBufYuv420p];
}
//解锁
CVPixelBufferUnlockBaseAddress(pixelBuffer,0);
}
- (void)presentYUVData:(NSData*)yuvdata width:(GLuint)width height:(GLuint)height {
@synchronized (self) {
//调整宽高
m_nVideoW = width;
m_nVideoH = height;
//yuv420视频数据
m_pBufYuv420p = NULL;
m_pBufYuv420p = (unsigned char*)[yuvdata bytes];
//渲染yuv420
[self rendeYUVData:m_pBufYuv420p];
}
}
- (instancetype)initWithDefaultFBO:(id)asbs {
if((self = [super init])) {
iLog(@"Render: %s; Version:%s", glGetString(GL_RENDERER), glGetString(GL_VERSION));
[self initializeGL];
//清除缓存
[self clearRenderBuffer];
}
return self;
}
- (void)resizeWithWidth:(GLuint)width AndHeight:(GLuint)height {
glViewport(0, 0, width, height);
m_nViewW = width;
m_nViewH = height;
if (m_nViewW==0) {
m_nViewW = 2*iScreenWidth/3;
}
if (m_nViewH==0) {
m_nViewH = 2*iScreenHeight/3;
}
//清除缓存
[self clearRenderBuffer];
}
- (void) render {
}
- (void) dealloc {
}
-(void)clearRenderBuffer {
//清除缓存
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
#pragma mark 渲染数据
-(void)rendeYUVData:(unsigned char*)yuv420data {
//清除缓存
[self clearRenderBuffer];
//
float x,y;
float wRatio = (float)m_nViewW/m_nVideoW;
float hRatio = (float)m_nViewH/m_nVideoH;
float minRatio = wRatio
VideoGLView.h
#import
#import
@interface VideoGLView : NSOpenGLView {
CVDisplayLinkRef displayLink;
}
- (void)setImage:(CVImageBufferRef)img;
-(void)presentYUVData:(NSData*)yuvdata width:(CGFloat)width height:(CGFloat)height;
@end
VideoGLView.m
#import "VideoGLView.h"
#import "OpenGLRenderer.h"
//#define SUPPORT_RETINA_RESOLUTION 1
@interface VideoGLView()
{
OpenGLRenderer* _renderer;
}
@end
@implementation VideoGLView
-(instancetype)init {
if (self=[super init]) {
[self awakeFromNib];
}
return self;
}
- (CVReturn) getFrameForTime:(const CVTimeStamp*)outputTime
{
// There is no autorelease pool when this method is called
// because it will be called from a background thread.
// It's important to create one or app can leak objects.
@autoreleasepool {
dispatch_sync(dispatch_get_main_queue(), ^{
[self drawView];
});
}
return kCVReturnSuccess;
}
// This is the renderer output callback function
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink,
const CVTimeStamp* now,
const CVTimeStamp* outputTime,
CVOptionFlags flagsIn,
CVOptionFlags* flagsOut,
void* displayLinkContext)
{
CVReturn result = [(__bridge VideoGLView*)displayLinkContext getFrameForTime:outputTime];
return result;
}
- (void) awakeFromNib
{
NSOpenGLPixelFormatAttribute attrs[] =
{
NSOpenGLPFADoubleBuffer,
NSOpenGLPFADepthSize, 24,
// Must specify the 3.2 Core Profile to use OpenGL 3.2
#if ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
NSOpenGLPFAOpenGLProfile,
NSOpenGLProfileVersion3_2Core,
#endif
0
};
NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
if (!pf)
{
NSLog(@"No OpenGL pixel format");
}
NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil];
#if ESSENTIAL_GL_PRACTICES_SUPPORT_GL3 && defined(DEBUG)
// When we're using a CoreProfile context, crash if we call a legacy OpenGL function
// This will make it much more obvious where and when such a function call is made so
// that we can remove such calls.
// Without this we'd simply get GL_INVALID_OPERATION error for calling legacy functions
// but it would be more difficult to see where that function was called.
CGLEnable([context CGLContextObj], kCGLCECrashOnRemovedFunctions);
#endif
[self setPixelFormat:pf];
[self setOpenGLContext:context];
#if SUPPORT_RETINA_RESOLUTION
// Opt-In to Retina resolution
[self setWantsBestResolutionOpenGLSurface:YES];
#endif // SUPPORT_RETINA_RESOLUTION
}
- (void) prepareOpenGL
{
[super prepareOpenGL];
// Make all the OpenGL calls to setup rendering
// and build the necessary rendering objects
[self initGL];
// Create a display link capable of being used with all active displays
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
// Set the renderer output callback function
CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, (__bridge void*)self);
// Set the display link for the current renderer
CGLContextObj cglContext = [[self openGLContext] CGLContextObj];
CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat);
// Activate the display link
CVDisplayLinkStart(displayLink);
// Register to be notified when the window closes so we can stop the displaylink
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowWillClose:)
name:NSWindowWillCloseNotification
object:[self window]];
}
- (void) windowWillClose:(NSNotification*)notification
{
// Stop the display link when the window is closing because default
// OpenGL render buffers will be destroyed. If display link continues to
// fire without renderbuffers, OpenGL draw calls will set errors.
CVDisplayLinkStop(displayLink);
}
- (void) initGL
{
// The reshape function may have changed the thread to which our OpenGL
// context is attached before prepareOpenGL and initGL are called. So call
// makeCurrentContext to ensure that our OpenGL context current to this
// thread (i.e. makeCurrentContext directs all OpenGL calls on this thread
// to [self openGLContext])
[[self openGLContext] makeCurrentContext];
// Synchronize buffer swaps with vertical refresh rate
GLint swapInt = 1;
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
// Init our renderer. Use 0 for the defaultFBO which is appropriate for
// OSX (but not iOS since iOS apps must create their own FBO)
_renderer = [[OpenGLRenderer alloc] initWithDefaultFBO:0];
}
- (void)reshape
{
[super reshape];
// We draw on a secondary thread through the display link. However, when
// resizing the view, -drawRect is called on the main thread.
// Add a mutex around to avoid the threads accessing the context
// simultaneously when resizing.
CGLLockContext([[self openGLContext] CGLContextObj]);
// Get the view size in Points
NSRect viewRectPoints = [self bounds];
#if SUPPORT_RETINA_RESOLUTION
// Rendering at retina resolutions will reduce aliasing, but at the potential
// cost of framerate and battery life due to the GPU needing to render more
// pixels.
// Any calculations the renderer does which use pixel dimentions, must be
// in "retina" space. [NSView convertRectToBacking] converts point sizes
// to pixel sizes. Thus the renderer gets the size in pixels, not points,
// so that it can set it's viewport and perform and other pixel based
// calculations appropriately.
// viewRectPixels will be larger than viewRectPoints for retina displays.
// viewRectPixels will be the same as viewRectPoints for non-retina displays
NSRect viewRectPixels = [self convertRectToBacking:viewRectPoints];
#else //if !SUPPORT_RETINA_RESOLUTION
// App will typically render faster and use less power rendering at
// non-retina resolutions since the GPU needs to render less pixels.
// There is the cost of more aliasing, but it will be no-worse than
// on a Mac without a retina display.
// Points:Pixels is always 1:1 when not supporting retina resolutions
NSRect viewRectPixels = viewRectPoints;
#endif // !SUPPORT_RETINA_RESOLUTION
// Set the new dimensions in our renderer
[_renderer resizeWithWidth:viewRectPixels.size.width
AndHeight:viewRectPixels.size.height];
CGLUnlockContext([[self openGLContext] CGLContextObj]);
}
- (void)renewGState
{
// Called whenever graphics state updated (such as window resize)
// OpenGL rendering is not synchronous with other rendering on the OSX.
// Therefore, call disableScreenUpdatesUntilFlush so the window server
// doesn't render non-OpenGL content in the window asynchronously from
// OpenGL content, which could cause flickering. (non-OpenGL content
// includes the title bar and drawing done by the app with other APIs)
[[self window] disableScreenUpdatesUntilFlush];
[super renewGState];
}
- (void) drawRect: (NSRect) theRect
{
// Called during resize operations
// Avoid flickering during resize by drawiing
[self drawView];
}
- (void) drawView
{
[[self openGLContext] makeCurrentContext];
// We draw on a secondary thread through the display link
// When resizing the view, -reshape is called automatically on the main
// thread. Add a mutex around to avoid the threads accessing the context
// simultaneously when resizing
CGLLockContext([[self openGLContext] CGLContextObj]);
[_renderer render];
CGLFlushDrawable([[self openGLContext] CGLContextObj]);
CGLUnlockContext([[self openGLContext] CGLContextObj]);
}
- (void) dealloc
{
// Stop the display link BEFORE releasing anything in the view
// otherwise the display link thread may call into the view and crash
// when it encounters something that has been release
CVDisplayLinkStop(displayLink);
CVDisplayLinkRelease(displayLink);
}
- (void)setImage:(CVImageBufferRef)img {
dispatch_sync(dispatch_get_main_queue(), ^{
[_renderer setImage:img];
});
}
-(void)presentYUVData:(NSData*)yuvdata width:(CGFloat)width height:(CGFloat)height {
[_renderer presentYUVData:yuvdata width:width height:height];
}
@end