You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS自定义UIView中GLKView与CAEAGLLayer美颜渲染异常排查

iOS GLKView相机渲染美颜滤镜问题修复

问题描述

开发自定义UIView子类,基于GLKView和CAEAGLLayer实现摄像头视频捕获、美颜滤镜处理及渲染输出,当前出现以下错误:

CGContextDrawImage: invalid context 0x0. Failed to bind EAGLDrawable to GL_RENDERBUFFER 2

原实现代码如下:

#import "FUCameraPlatformView.h"
#import "OverlayView.h"
#import "GLView.h" 
#import <GLKit/GLKit.h>

@interface FUCameraPlatformView() <FUCameraDelegate>
@property (nonatomic, strong) EAGLContext *glContext;
@property (nonatomic, strong) GLKView *glView;
@property (nonatomic, strong) CIContext *ciContext;
@property (nonatomic, strong) GLView *view; 
@property (nonatomic, strong) FUCamera *camera;
@property (nonatomic, strong) MHBeautyManager *beautyManager;
@property (nonatomic , strong) OverlayView *overlay;
@end

@implementation FUCameraPlatformView

- (instancetype)initWithFrame:(CGRect)frame
               viewIdentifier:(int64_t)viewId
                    arguments:(id)args
               cameraInstance:(FUCamera *)camera
              binaryMessenger:(NSObject<FlutterBinaryMessenger> *)messenger
                beautyManager:(MHBeautyManager *)manager {
    self = [super init];
    if (self) {
        _beautyManager = manager;
        _beautyManager.delegate = self;
        _camera = camera;
        _camera.delegate = self;
       _view = [[GLView alloc] initWithFrame:frame];
        [self setupGL];
        [self setupCamera];
    }
    return self;
}

- (void)setupGL {
    _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    _glView = [[GLKView alloc] initWithFrame:_view.bounds context:self.glContext];
    _glView.enableSetNeedsDisplay = NO;
    _glView.context = self.glContext;
    _glView.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    _ciContext = [CIContext contextWithEAGLContext:_glContext];

    // Ensure the drawable properties are set correctly
    CAEAGLLayer *eaglLayer = (CAEAGLLayer *)_view.layer;
    eaglLayer.opaque = YES;
    eaglLayer.contentsScale = [UIScreen mainScreen].scale;
    eaglLayer.drawableProperties = @{
        kEAGLDrawablePropertyRetainedBacking: [NSNumber numberWithBool:NO],
        kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
    };

    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Failed to make complete framebuffer object %x", status);
    }

    if (![EAGLContext setCurrentContext:self.glContext]) {
        NSLog(@"Failed to set current OpenGL context.");
        return;
    }
    
    [_view addSubview:_glView];

    _overlay = [[OverlayView alloc] initWithFrame:_view.bounds];
    _overlay.backgroundColor = [UIColor clearColor];
    [_view addSubview:_overlay];
}


- (void)showFaceLandmarksAndFaceRectWithPersonsArray:(NSMutableArray *)arrPersons {
    if (_overlay.hidden) {
        _overlay.hidden = NO;
    }
    _overlay.arrPersons = arrPersons;
    [_overlay setNeedsDisplay];
    [_overlay layoutIfNeeded];
}

- (void)setupCamera {
    [self.camera startCapture];
}

- (void)didOutputVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    // Check for null sample buffer
    if (!sampleBuffer) {
        return;
    }

    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    if (!imageBuffer) {
        return;
    }

    OSType formatType = CVPixelBufferGetPixelFormatType(imageBuffer);
    CVPixelBufferLockBaseAddress(imageBuffer, 0);

    CIImage *image = [CIImage imageWithCVPixelBuffer:imageBuffer];
    if (!image) {
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
        return;
    }

    CIFilter *filter = [CIFilter filterWithName:@"CIPhotoEffectChrome" keysAndValues:kCIInputImageKey, image, nil];
    if (!filter) {
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
        return;
    }
    [filter setValue:image forKey:kCIInputImageKey];
    [_glView bindDrawable];

    CGFloat width = _glView.drawableHeight / 640.f * 480.f;
    CGFloat height = _glView.drawableHeight;
    CGFloat x = (_glView.drawableWidth - width) / 2;

    if (_ciContext) {
        [_ciContext drawImage:filter.outputImage inRect:CGRectMake(x, 0, width, height) fromRect:CGRectMake(0, 0, 480, 640)];
    }

    if (self.beautyManager) {
        [self.beautyManager processWithPixelBuffer:imageBuffer formatType:formatType];
    }
    [_glView display];

    
    // CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
}

- (UIView *)view {
    return _view;
}

@end

核心问题分析

  1. 错误修改非渲染视图的Layer:修改了_view.layer的CAEAGLLayer属性,但实际渲染用的是_glView(GLKView自带CAEAGLLayer),导致GLKView无法绑定正确的渲染缓冲区。
  2. 帧缓冲区检查时机过早:在setupGL中调用glCheckFramebufferStatus时,GLKView尚未完成绑定,无法得到有效状态。
  3. 上下文未切换到当前帧处理线程:视频回调线程可能未设置正确的OpenGL上下文,导致bindDrawable失败。
  4. 像素缓冲区未解锁:注释了CVPixelBufferUnlockBaseAddress,引发内存泄漏风险。
  5. 美颜与滤镜处理顺序颠倒:先渲染滤镜图像再处理美颜,美颜效果无法显示到屏幕。

修复方案

1. 移除错误的Layer设置代码

删除setupGL中对_view.layer的修改,GLKView会自动管理自身的CAEAGLLayer:

- (void)setupGL {
    _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    _glView = [[GLKView alloc] initWithFrame:_view.bounds context:self.glContext];
    _glView.enableSetNeedsDisplay = NO;
    _glView.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    _ciContext = [CIContext contextWithEAGLContext:_glContext];

    if (![EAGLContext setCurrentContext:self.glContext]) {
        NSLog(@"Failed to set current OpenGL context.");
        return;
    }
    
    [_view addSubview:_glView];

    _overlay = [[OverlayView alloc] initWithFrame:_view.bounds];
    _overlay.backgroundColor = [UIColor clearColor];
    [_view addSubview:_overlay];
}

2. 调整帧缓冲区检查时机

将帧缓冲区检查移到bindDrawable之后,放在视频回调中:

[_glView bindDrawable];
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
    NSLog(@"Failed to make complete framebuffer object %x", status);
}

3. 确保回调线程的上下文正确

在didOutputVideoSampleBuffer开头添加上下文切换代码:

if (![EAGLContext setCurrentContext:self.glContext]) {
    NSLog(@"Failed to set current OpenGL context for frame processing.");
    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
    return;
}

4. 恢复像素缓冲区解锁

在回调末尾添加解锁代码:

CVPixelBufferUnlockBaseAddress(imageBuffer, 0);

5. 调整美颜与滤镜处理顺序

先处理美颜,再生成CIImage应用滤镜,确保美颜效果被渲染:

if (self.beautyManager) {
    [self.beautyManager processWithPixelBuffer:imageBuffer formatType:formatType];
}
CIImage *image = [CIImage imageWithCVPixelBuffer:imageBuffer];

修复后的完整视频回调代码

- (void)didOutputVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    if (!sampleBuffer) return;
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    if (!imageBuffer) return;

    OSType formatType = CVPixelBufferGetPixelFormatType(imageBuffer);
    CVPixelBufferLockBaseAddress(imageBuffer, 0);

    // 确保当前线程使用正确的OpenGL上下文
    if (![EAGLContext setCurrentContext:self.glContext]) {
        NSLog(@"Failed to set current OpenGL context for frame processing.");
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
        return;
    }

    // 先处理美颜
    if (self.beautyManager) {
        [self.beautyManager processWithPixelBuffer:imageBuffer formatType:formatType];
    }

    CIImage *image = [CIImage imageWithCVPixelBuffer:imageBuffer];
    if (!image) {
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
        return;
    }

    CIFilter *filter = [CIFilter filterWithName:@"CIPhotoEffectChrome" keysAndValues:kCIInputImageKey, image, nil];
    if (!filter) {
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
        return;
    }

    [_glView bindDrawable];
    // 检查帧缓冲区状态
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Failed to make complete framebuffer object %x", status);
    }

    CGFloat width = _glView.drawableHeight / 640.f * 480.f;
    CGFloat height = _glView.drawableHeight;
    CGFloat x = (_glView.drawableWidth - width) / 2;

    if (_ciContext) {
        [_ciContext drawImage:filter.outputImage inRect:CGRectMake(x, 0, width, height) fromRect:CGRectMake(0, 0, 480, 640)];
    }

    [_glView display];
    // 解锁像素缓冲区
    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
}

内容的提问来源于stack exchange,提问作者Georgina

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 19:33:10