用 OpenGL 渲染视频丨音视频工程示例

发布一下 0 0

渲染是音视频技术栈相关的一个非常重要的方向,视频图像在设备上的展示、各种流行的视频特效都离不开渲染技术的支持。

在 RenderDemo 这个工程示例系列,我们将为大家展示一些渲染相关的 Demo,来向大家介绍如何在 iOS/Android 平台上手一些渲染相关的开发。

这里是第二篇:用 OpenGL 渲染视频。我们分别在 iOS 和 Android 实现了用 OpenGL 渲染视频数据的 Demo。在本文中,包括如下内容:

  • 1)iOS 视频 OpenGL 渲染 Demo;
  • 2)Android 视频 OpenGL 渲染 Demo;
  • 3)详尽的代码注释,帮你理解代码逻辑和原理。

1、iOS Demo

其实我们在之前的 iOS 视频采集的 Demo 中已经使用了系统的 API AVCaptureVideoPreviewLayer 来实现了视频数据的渲染,不过现在我们准备深入渲染的细节,所以我们这里会使用 OpenGL 来自己实现渲染模块替换掉 AVCaptureVideoPreviewLayer

1.1、视频采集模块

视频采集模块与 iOS 视频采集的 Demo 中讲到的一致,这里就不再细讲,只贴一下主要代码:

首先,实现一个 KFVideoCaptureConfig 类用于定义视频采集参数的配置。

领音视频资料→「链接」

KFVideoCaptureConfig.h

#import <Foundation/Foundation.h>#import <AVFoundation/AVFoundation.h>NS_ASSUME_NONNULL_BEGINtypedef NS_ENUM(NSInteger, KFVideoCaptureMirrorType) {    KFVideoCaptureMirrorNone = 0,    KFVideoCaptureMirrorFront = 1 << 0,    KFVideoCaptureMirrorBack = 1 << 1,    KFVideoCaptureMirrorAll = (KFVideoCaptureMirrorFront | KFVideoCaptureMirrorBack),};@interface KFVideoCaptureConfig : NSObject@property (nonatomic, copy) AVCaptureSessionPreset preset; // 视频采集参数,比如分辨率等,与画质相关。@property (nonatomic, assign) AVCaptureDevicePosition position; // 摄像头位置,前置/后置摄像头。@property (nonatomic, assign) AVCaptureVideoOrientation orientation; // 视频画面方向。@property (nonatomic, assign) NSInteger fps; // 视频帧率。@property (nonatomic, assign) OSType pixelFormatType; // 颜色空间格式。@property (nonatomic, assign) KFVideoCaptureMirrorType mirrorType; // 镜像类型。@endNS_ASSUME_NONNULL_END

KFVideoCaptureConfig.m

#import "KFVideoCaptureConfig.h"@implementation KFVideoCaptureConfig- (instancetype)init {    self = [super init];    if (self) {        _preset = AVCaptureSessionPreset1920x1080;        _position = AVCaptureDevicePositionFront;        _orientation = AVCaptureVideoOrientationPortrait;        _fps = 30;        _mirrorType = KFVideoCaptureMirrorFront;        // 设置颜色空间格式,这里要注意了:        // 1、一般我们采集图像用于后续的编码时,这里设置 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange 即可。        // 2、如果想支持 HDR 时(iPhone12 及之后设备才支持),这里设置为:kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange。        _pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;    }        return self;}@end

接下来,我们实现一个 KFVideoCapture 类来实现视频采集。

KFVideoCapture.h

#import <Foundation/Foundation.h>#import "KFVideoCaptureConfig.h"NS_ASSUME_NONNULL_BEGIN@interface KFVideoCapture : NSObject+ (instancetype)new NS_UNAVAILABLE;- (instancetype)init NS_UNAVAILABLE;- (instancetype)initWithConfig:(KFVideoCaptureConfig *)config;@property (nonatomic, strong, readonly) KFVideoCaptureConfig *config;@property (nonatomic, strong, readonly) AVCaptureVideoPreviewLayer *previewLayer; // 视频预览渲染 layer。@property (nonatomic, copy) void (^sampleBufferOutputCallBack)(CMSampleBufferRef sample); // 视频采集数据回调。@property (nonatomic, copy) void (^sessionErrorCallBack)(NSError *error); // 视频采集会话错误回调。@property (nonatomic, copy) void (^sessionInitSuccessCallBack)(void); // 视频采集会话初始化成功回调。- (void)startRunning; // 开始采集。- (void)stopRunning; // 停止采集。- (void)changeDevicePosition:(AVCaptureDevicePosition)position; // 切换摄像头。@endNS_ASSUME_NONNULL_END

KFVideoCapture.m

#import "KFVideoCapture.h"#import <UIKit/UIKit.h>@interface KFVideoCapture () <AVCaptureVideoDataOutputSampleBufferDelegate>@property (nonatomic, strong, readwrite) KFVideoCaptureConfig *config;@property (nonatomic, strong, readonly) AVCaptureDevice *captureDevice; // 视频采集设备。@property (nonatomic, strong) AVCaptureDeviceInput *backDeviceInput; // 后置摄像头采集输入。@property (nonatomic, strong) AVCaptureDeviceInput *frontDeviceInput; // 前置摄像头采集输入。@property (nonatomic, strong) AVCaptureVideoDataOutput *videoOutput; // 视频采集输出。@property (nonatomic, strong) AVCaptureSession *captureSession; // 视频采集会话。@property (nonatomic, strong, readwrite) AVCaptureVideoPreviewLayer *previewLayer; // 视频预览渲染 layer。@property (nonatomic, assign, readonly) CMVideoDimensions sessionPresetSize; // 视频采集分辨率。@property (nonatomic, strong) dispatch_queue_t captureQueue;@end@implementation KFVideoCapture#pragma mark - Property- (AVCaptureDevice *)backCamera {    return [self cameraWithPosition:AVCaptureDevicePositionBack];}- (AVCaptureDeviceInput *)backDeviceInput {    if (!_backDeviceInput) {        _backDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamera] error:nil];    }        return _backDeviceInput;}- (AVCaptureDevice *)frontCamera {    return [self cameraWithPosition:AVCaptureDevicePositionFront];}- (AVCaptureDeviceInput *)frontDeviceInput {    if (!_frontDeviceInput) {        _frontDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontCamera] error:nil];    }        return _frontDeviceInput;}- (AVCaptureVideoDataOutput *)videoOutput {    if (!_videoOutput) {        _videoOutput = [[AVCaptureVideoDataOutput alloc] init];        [_videoOutput setSampleBufferDelegate:self queue:self.captureQueue]; // 设置返回采集数据的代理和回调。        _videoOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey: @(_config.pixelFormatType)};        _videoOutput.alwaysDiscardsLateVideoFrames = YES; // YES 表示:采集的下一帧到来前,如果有还未处理完的帧,丢掉。    }    return _videoOutput;}- (AVCaptureSession *)captureSession {    if (!_captureSession) {        AVCaptureDeviceInput *deviceInput = self.config.position == AVCaptureDevicePositionBack ? self.backDeviceInput : self.frontDeviceInput;        if (!deviceInput) {            return nil;        }        // 1、初始化采集会话。        _captureSession = [[AVCaptureSession alloc] init];                // 2、添加采集输入。        for (AVCaptureSessionPreset selectPreset in [self sessionPresetList]) {            if ([_captureSession canSetSessionPreset:selectPreset]) {                [_captureSession setSessionPreset:selectPreset];                if ([_captureSession canAddInput:deviceInput]) {                    [_captureSession addInput:deviceInput];                    break;                }            }        }                // 3、添加采集输出。        if ([_captureSession canAddOutput:self.videoOutput]) {            [_captureSession addOutput:self.videoOutput];        }                // 4、更新画面方向。        [self _updateOrientation];                // 5、更新画面镜像。        [self _updateMirror];            // 6、更新采集实时帧率。        [self.captureDevice lockForConfiguration:nil];        [self _updateActiveFrameDuration];        [self.captureDevice unlockForConfiguration];                // 7、回报成功。        if (self.sessionInitSuccessCallBack) {            self.sessionInitSuccessCallBack();        }    }        return _captureSession;}- (AVCaptureVideoPreviewLayer *)previewLayer {    if (!_captureSession) {        return nil;    }    if (!_previewLayer) {        // 初始化预览渲染 layer。这里就直接用系统提供的 API 来渲染。        _previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];        [_previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];    }        return _previewLayer;}- (AVCaptureDevice *)captureDevice {    // 视频采集设备。    return (self.config.position == AVCaptureDevicePositionBack) ? [self backCamera] : [self frontCamera];}- (CMVideoDimensions)sessionPresetSize {    // 视频采集分辨率。    return CMVideoFormatDescriptionGetDimensions([self captureDevice].activeFormat.formatDescription);}#pragma mark - LifeCycle- (instancetype)initWithConfig:(KFVideoCaptureConfig *)config {    self = [super init];    if (self) {        _config = config;        _captureQueue = dispatch_queue_create("com.KeyFrameKit.videoCapture", DISPATCH_QUEUE_SERIAL);        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:nil];    }        return self;}- (void)dealloc {    [[NSNotificationCenter defaultCenter] removeObserver:self];}#pragma mark - Public Method- (void)startRunning {    typeof(self) __weak weakSelf = self;    dispatch_async(_captureQueue, ^{        [weakSelf _startRunning];    });}- (void)stopRunning {    typeof(self) __weak weakSelf = self;    dispatch_async(_captureQueue, ^{        [weakSelf _stopRunning];    });}- (void)changeDevicePosition:(AVCaptureDevicePosition)position {    typeof(self) __weak weakSelf = self;    dispatch_async(_captureQueue, ^{        [weakSelf _updateDeveicePosition:position];    });}#pragma mark - Private Method- (void)_startRunning {    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];    if (status == AVAuthorizationStatusAuthorized) {        if (!self.captureSession.isRunning) {            [self.captureSession startRunning];        }    } else {        NSLog(@"没有相机使用权限");    }}- (void)_stopRunning {    if (_captureSession && _captureSession.isRunning) {        [_captureSession stopRunning];    }}- (void)_updateDeveicePosition:(AVCaptureDevicePosition)position {    // 切换采集的摄像头。        if (position == self.config.position || !_captureSession.isRunning) {        return;    }        // 1、切换采集输入。    AVCaptureDeviceInput *curInput = self.config.position == AVCaptureDevicePositionBack ? self.backDeviceInput : self.frontDeviceInput;    AVCaptureDeviceInput *addInput = self.config.position == AVCaptureDevicePositionBack ? self.frontDeviceInput : self.backDeviceInput;    if (!curInput || !addInput) {        return;    }    [self.captureSession removeInput:curInput];    for (AVCaptureSessionPreset selectPreset in [self sessionPresetList]) {        if ([_captureSession canSetSessionPreset:selectPreset]) {            [_captureSession setSessionPreset:selectPreset];            if ([_captureSession canAddInput:addInput]) {                [_captureSession addInput:addInput];                self.config.position = position;                break;            }        }    }        // 2、更新画面方向。    [self _updateOrientation];        // 3、更新画面镜像。    [self _updateMirror];    // 4、更新采集实时帧率。    [self.captureDevice lockForConfiguration:nil];    [self _updateActiveFrameDuration];    [self.captureDevice unlockForConfiguration];}- (void)_updateOrientation {    // 更新画面方向。    AVCaptureConnection *connection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo]; // AVCaptureConnection 用于把输入和输出连接起来。    if ([connection isVideoOrientationSupported] && connection.videoOrientation != self.config.orientation) {        connection.videoOrientation = self.config.orientation;    }}- (void)_updateMirror {    // 更新画面镜像。    AVCaptureConnection *connection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];    if ([connection isVideoMirroringSupported]) {        if ((self.config.mirrorType & KFVideoCaptureMirrorFront) && self.config.position == AVCaptureDevicePositionFront) {            connection.videoMirrored = YES;        } else if ((self.config.mirrorType & KFVideoCaptureMirrorBack) && self.config.position == AVCaptureDevicePositionBack) {            connection.videoMirrored = YES;        } else {            connection.videoMirrored = NO;        }    }}- (BOOL)_updateActiveFrameDuration {    // 更新采集实时帧率。        // 1、帧率换算成帧间隔时长。    CMTime frameDuration = CMTimeMake(1, (int32_t) self.config.fps);        // 2、设置帧率大于 30 时,找到满足该帧率及其他参数,并且当前设备支持的 AVCaptureDeviceFormat。    if (self.config.fps > 30) {        for (AVCaptureDeviceFormat *vFormat in [self.captureDevice formats]) {            CMFormatDescriptionRef description = vFormat.formatDescription;            CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(description);            float maxRate = ((AVFrameRateRange *) [vFormat.videoSupportedFrameRateRanges objectAtIndex:0]).maxFrameRate;            if (maxRate >= self.config.fps && CMFormatDescriptionGetMediaSubType(description) == self.config.pixelFormatType && self.sessionPresetSize.width * self.sessionPresetSize.height == dims.width * dims.height) {                self.captureDevice.activeFormat = vFormat;                break;            }        }    }        // 3、检查设置的帧率是否在当前设备的 activeFormat 支持的最低和最高帧率之间。如果是,就设置帧率。    __block BOOL support = NO;    [self.captureDevice.activeFormat.videoSupportedFrameRateRanges enumerateObjectsUsingBlock:^(AVFrameRateRange * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {        if (CMTimeCompare(frameDuration, obj.minFrameDuration) >= 0 &&            CMTimeCompare(frameDuration, obj.maxFrameDuration) <= 0) {            support = YES;            *stop = YES;        }    }];    if (support) {        [self.captureDevice setActiveVideoMinFrameDuration:frameDuration];        [self.captureDevice setActiveVideoMaxFrameDuration:frameDuration];        return YES;    }        return NO;}#pragma mark - NSNotification- (void)sessionRuntimeError:(NSNotification *)notification {    if (self.sessionErrorCallBack) {        self.sessionErrorCallBack(notification.userInfo[AVCaptureSessionErrorKey]);    }}#pragma mark - Utility- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {    // 从当前手机寻找符合需要的采集设备。    NSArray *devices = nil;    NSString *version = [UIDevice currentDevice].systemVersion;    if (version.doubleValue >= 10.0) {        AVCaptureDeviceDiscoverySession *deviceDiscoverySession = [AVCaptureDeviceDiscoverySession  discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:position];        devices = deviceDiscoverySession.devices;    } else {#pragma GCC diagnostic push#pragma GCC diagnostic ignored "-Wdeprecated-declarations"        devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];#pragma GCC diagnostic pop    }        for (AVCaptureDevice *device in devices) {        if ([device position] == position) {            return device;        }    }        return nil;}- (NSArray *)sessionPresetList {    return @[self.config.preset, AVCaptureSessionPreset3840x2160, AVCaptureSessionPreset1920x1080, AVCaptureSessionPreset1280x720, AVCaptureSessionPresetLow];}#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {    // 向外回调数据。    if (output == self.videoOutput) {        if (self.sampleBufferOutputCallBack) {            self.sampleBufferOutputCallBack(sampleBuffer);        }    }}@end

上面是 KFVideoCapture 的实现。

1.2、视频渲染模块

1)渲染视图 KFOpenGLView

接下来,我们来用 OpenGL 实现一个支持视频数据渲染的 View,对应的接口如下:

KFOpenGLView.h

#import <UIKit/UIKit.h>#import <OpenGLES/ES2/gl.h>#import <OpenGLES/ES2/glext.h>#import "KFTextureFrame.h"// 渲染画面填充模式。typedef NS_ENUM(NSInteger, KFGLViewContentMode) {    // 自动填充满,可能会变形。    KFGLViewContentModeStretch = 0,    // 按比例适配,可能会有黑边。    KFGLViewContentModeFit = 1,    // 根据比例裁剪后填充满。    KFGLViewContentModeFill = 2};// 使用 OpenGL 实现渲染 View。@interface KFOpenGLView : UIView- (instancetype)initWithFrame:(CGRect)frame context:(nullable EAGLContext *)context;@property (nonatomic, assign) KFGLViewContentMode fillMode; // 画面填充模式。- (void)displayFrame:(nonnull KFTextureFrame *)frame; // 渲染一帧纹理。@end

核心功能是提供了设置画面填充模式的接口和渲染一帧纹理的接口。下面是对应的实现:

KFOpenGLView.m

#import "KFOpenGLView.h"#import <QuartzCore/QuartzCore.h>#import <AVFoundation/AVUtilities.h>#import <mach/mach_time.h>#import <GLKit/GLKit.h>#import "KFGLFilter.h"#import "KFGLBase.h"#import <GLKit/GLKit.h>@interface KFOpenGLView() {    // The pixel dimensions of the CAEAGLLayer.    GLint _backingWidth;    GLint _backingHeight;        GLuint _frameBufferHandle;    GLuint _colorBufferHandle;        KFGLFilter *_filter;    GLfloat _customVertices[8];}@property (nonatomic, assign) CGSize currentViewSize; // 当前 view 大小。@property (nonatomic, assign) CGSize frameSize; // 当前被渲染的纹理大小。@end@implementation KFOpenGLView+ (Class)layerClass {    return [CAEAGLLayer class];}- (instancetype)initWithFrame:(CGRect)frame context:(nullable EAGLContext *)context{    if (self = [super initWithFrame:frame]) {        self.contentScaleFactor = [[UIScreen mainScreen] scale];        // 设定 layer 相关属性。        CAEAGLLayer *eaglLayer = (CAEAGLLayer *) self.layer;        eaglLayer.opaque = YES;        eaglLayer.drawableProperties = @{ kEAGLDrawablePropertyRetainedBacking: @(NO),                                          kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};        _fillMode = KFGLViewContentModeFit;                // 设置当前 OpenGL 上下文,并初始化相关 GL 环境。        if (context) {            EAGLContext *preContext = [EAGLContext currentContext];            [EAGLContext setCurrentContext:context];            [self _setupGL];            [EAGLContext setCurrentContext:preContext];        } else {            NSLog(@"KFOpenGLView context nil");        }    }        return self;}- (void)layoutSubviews {    // 视图自动调整布局,同步至渲染视图。    [super layoutSubviews];    _currentViewSize = self.bounds.size;}- (void)dealloc {    if(_frameBufferHandle != 0){        glDeleteFramebuffers(1, &_frameBufferHandle);    }    if(_colorBufferHandle != 0){        glDeleteRenderbuffers(1, &_colorBufferHandle);    }}# pragma mark - OpenGL Setup- (void)_setupGL {    // 1、申请并绑定帧缓冲区对象 FBO。    glGenFramebuffers(1, &_frameBufferHandle);    glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferHandle);        // 2、申请并绑定渲染缓冲区对象 RBO。    glGenRenderbuffers(1, &_colorBufferHandle);    glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle);        // 3、将渲染图层(_eaglLayer)的存储绑定到 RBO。    [[EAGLContext currentContext] renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];    // 当渲染缓冲区 RBO 绑定存储空间完成后,可以通过 glGetRenderbufferParameteriv 获取渲染缓冲区的宽高,实际跟上面设置的 layer 的宽高一致。    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);    // 4、将 RBO 绑定为 FBO 的一个附件。绑定后,OpenGL 对 FBO 的绘制会同步到 RBO 后再上屏。    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorBufferHandle);    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {        NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));    }        // 5、KFGLFilter 封装了 shader 的加载、编译和着色器程序链接,以及 FBO 的管理。这里用一个 Filter 来实现具体的渲染细节。    _filter = [[KFGLFilter alloc] initWithCustomFBO:YES vertexShader:KFDefaultVertexShader fragmentShader:KFDefaultFragmentShader]; // 这里 isCustomFBO 传 YES,表示直接用外部的 FBO(即上面创建的 FBO 对象 _frameBufferHandle)。vertexShader 和 fragmentShader 则都使用默认的。    __weak typeof(self) wself = self;    _filter.preDrawCallBack = ^(){        // 在渲染前回调中,关联顶点位置数据。通过渲染回调接口,可以在外部更新顶点数据。        __strong typeof(wself) sself = wself;        if (sself) {            glVertexAttribPointer([[sself->_filter getProgram] getAttribLocation:@"position"], 2, GL_FLOAT, 0, 0, sself->_customVertices);        }    };}- (void)_updaterVertices {    // 根据视频画面填充模式计算顶点数据。    float heightScaling = 1.0;    float widthScaling = 1.0;        if (!CGSizeEqualToSize(_currentViewSize, CGSizeZero) && !CGSizeEqualToSize(_frameSize, CGSizeZero)) {        CGRect insetRect = AVMakeRectWithAspectRatioInsideRect(_frameSize, CGRectMake(0, 0, _currentViewSize.width, _currentViewSize.height));                switch (_fillMode) {            case KFGLViewContentModeStretch: {                widthScaling = 1.0;                heightScaling = 1.0;                break;            }            case KFGLViewContentModeFit: {                widthScaling = insetRect.size.width / _currentViewSize.width;                heightScaling = insetRect.size.height / _currentViewSize.height;                break;            }            case KFGLViewContentModeFill: {                widthScaling = _currentViewSize.height / insetRect.size.height;                heightScaling = _currentViewSize.width / insetRect.size.width;                break;            }        }    }        _customVertices[0] = -widthScaling;    _customVertices[1] = -heightScaling;    _customVertices[2] = widthScaling;    _customVertices[3] = -heightScaling;    _customVertices[4] = -widthScaling;    _customVertices[5] = heightScaling;    _customVertices[6] = widthScaling;    _customVertices[7] = heightScaling;}#pragma mark - OpenGLES Render// 渲染一帧纹理。- (void)displayFrame:(KFTextureFrame *)frame {    if (![EAGLContext currentContext] || !frame) {        return;    }        // 1、绑定 FBO、RBO 到 OpenGL 渲染管线。    glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferHandle);    glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle);        // 2、设置视口大小为整个渲染缓冲区的区域。    glViewport(0, 0, _backingWidth, _backingHeight);        // 3、渲染传进来的一帧纹理。    KFTextureFrame *renderFrame = frame.copy; // 获取纹理。    _frameSize = renderFrame.textureSize; // 记录纹理大小。        // 将 GL 的坐标系(↑→)适配屏幕坐标系(↓→),生成新的 mvp 矩阵。    GLKVector4 scale = {1, -1, 1, 1};    renderFrame.mvpMatrix = GLKMatrix4ScaleWithVector4(GLKMatrix4Identity, scale);        [self _updaterVertices]; // 更新一下顶点位置数据。外部如何更改了画面填充模式会影响顶点位置。    [_filter render:renderFrame]; // 渲染。        // 4、把 RBO 的内容显示到窗口系统 (CAEAGLLayer) 中。    [[EAGLContext currentContext] presentRenderbuffer:GL_RENDERBUFFER];     // 5、将 FBO、RBO 从 OpenGL 渲染管线解绑。    glBindFramebuffer(GL_FRAMEBUFFER, 0);    glBindRenderbuffer(GL_RENDERBUFFER, 0);}@end

相关的代码解释,我们已经在代码注释里进行了说明。这里面最关键部分是我们不再将所有的 OpenGL 代码都放在一个类里,而是根据功能模块进行了封装。在 KFOpenGLView 中,除了常规的 OpenGL 环境初始化,我们封装了一个 KFGLFilter 类实现 shader 的加载、编译和着色器程序链接,以及 FBO 的管理,并用一个 KFGLFilter 示例去完成具体的渲染细节。

2)渲染节点 KFGLFilter

KFGLFilter 的代码如下:

KFGLFilter.h

#import <Foundation/Foundation.h>#import "KFGLFrameBuffer.h"#import "KFGLProgram.h"#import "KFTextureFrame.h"NS_ASSUME_NONNULL_BEGIN// KFGLFilter 封装了 shader 的加载、编译和着色器程序链接,以及 FBO 的管理@interface KFGLFilter : NSObject// KFGLFilter 初始化。// 这里 isCustomFBO 传 YES,表示直接用外部的 FBO(即上面创建的 FBO 对象 _frameBufferHandle)。- (instancetype)initWithCustomFBO:(BOOL)isCustomFBO vertexShader:(NSString *)vertexShader fragmentShader:(NSString *)fragmentShader;- (instancetype)initWithCustomFBO:(BOOL)isCustomFBO vertexShader:(NSString *)vertexShader fragmentShader:(NSString *)fragmentShader textureAttributes:(KFGLTextureAttributes *)textureAttributes;@property (nonatomic, copy) void (^preDrawCallBack)(void); // 渲染前回调。@property (nonatomic, copy) void (^postDrawCallBack)(void); // 渲染后回调。- (KFGLFrameBuffer *)getOutputFrameBuffer; // 获取内部的 FBO。- (KFGLProgram *)getProgram; // 获取 GL 程序。- (KFTextureFrame *)render:(KFTextureFrame*)frame; // 渲染一帧纹理。// 设置 GL 程序变量值。- (void)setIntegerUniformValue:(NSString *)uniformName intValue:(int)intValue;- (void)setFloatUniformValue:(NSString *)uniformName floatValue:(float)floatValue;@endNS_ASSUME_NONNULL_END

KFGLFilter 的接口设计中我们可以看到主要提供了获取内部的 FBO、获取 GL 程序、设置 GL 程序变量值、渲染一帧纹理、渲染前回调、渲染后回调等接口。具体实现代码如下:

KFGLFilter.mm

#import "KFGLFilter.h"#import <OpenGLES/ES2/glext.h>@interface KFGLFilter() {    BOOL _mIsCustomFBO;    KFGLFrameBuffer *_mFrameBuffer;    KFGLProgram *_mProgram;    KFGLTextureAttributes *_mGLTextureAttributes;    int _mTextureUniform;    int _mPostionMatrixUniform;    int _mPositionAttribute;    int _mTextureCoordinateAttribute;}@end@implementation KFGLFilter- (instancetype)initWithCustomFBO:(BOOL)isCustomFBO vertexShader:(NSString *)vertexShader fragmentShader:(NSString *)fragmentShader {    return [self initWithCustomFBO:isCustomFBO vertexShader:vertexShader fragmentShader:fragmentShader textureAttributes:[KFGLTextureAttributes new]];}- (instancetype)initWithCustomFBO:(BOOL)isCustomFBO vertexShader:(NSString *)vertexShader fragmentShader:(NSString *)fragmentShader textureAttributes:(KFGLTextureAttributes *)textureAttributes {    self = [super init];    if (self) {        // 初始化。        _mTextureUniform = -1;        _mPostionMatrixUniform = -1;        _mPositionAttribute = -1;        _mTextureCoordinateAttribute = -1;        _mIsCustomFBO = isCustomFBO;        _mGLTextureAttributes = textureAttributes;        // 加载和编译 shader,并链接到着色器程序。        [self _setupProgram:vertexShader fragmentShader:fragmentShader];    }    return self;}- (void)dealloc {    if (_mFrameBuffer != nil) {        _mFrameBuffer = nil;    }    if (_mProgram != nil) {        _mProgram = nil;    }}- (KFGLFrameBuffer *)getOutputFrameBuffer {    // 当没有指定外部 FBO 时,内部会生成一个 FBO,这里返回的是内部的 FBO。    return _mFrameBuffer;}-(KFGLProgram *)getProgram {    // 返回 GL 程序。    return _mProgram;}- (void)setIntegerUniformValue:(NSString *)uniformName intValue:(int)intValue {    // 设置 GL 程序变量值。    if (_mProgram != nil) {        int uniforamIndex = [_mProgram getUniformLocation:uniformName];        [_mProgram use];        glUniform1i(uniforamIndex, intValue);    }}- (void)setFloatUniformValue:(NSString *)uniformName floatValue:(float)floatValue {    // 设置 GL 程序变量值。    if (_mProgram != nil) {        int uniforamIndex = [_mProgram getUniformLocation:uniformName];        [_mProgram use];        glUniform1f(uniforamIndex, floatValue);    }}- (void)_setupFrameBuffer:(CGSize)size {    // 如果指定使用外部的 FBO,则这里就直接返回。    if (_mIsCustomFBO) {        return;    }    // 如果没指定使用外部的 FBO,这里就再创建一个 FBO。    if (_mFrameBuffer == nil || _mFrameBuffer.getSize.width != size.width || _mFrameBuffer.getSize.height != size.height) {        if (_mFrameBuffer != nil) {            _mFrameBuffer = nil;        }        _mFrameBuffer = [[KFGLFrameBuffer alloc] initWithSize:size textureAttributes:_mGLTextureAttributes];    }}- (void)_setupProgram:(NSString *)vertexShader fragmentShader:(NSString *)fragmentShader {    // 加载和编译 shader,并链接到着色器程序。    if (_mProgram == nil) {        _mProgram = [[KFGLProgram alloc] initWithVertexShader:vertexShader fragmentShader:fragmentShader];        // 获取与 Shader 中对应参数的位置值:        _mTextureUniform = [_mProgram getUniformLocation:@"inputImageTexture"];        _mPostionMatrixUniform = [_mProgram getUniformLocation:@"mvpMatrix"];        _mPositionAttribute = [_mProgram getAttribLocation:@"position"];        _mTextureCoordinateAttribute = [_mProgram getAttribLocation:@"inputTextureCoordinate"];    }}- (KFTextureFrame *)render:(KFTextureFrame *)frame {    // 渲染一帧纹理。        if (frame == nil) {        return frame;    }    KFTextureFrame *resultFrame = frame.copy;    [self _setupFrameBuffer:frame.textureSize];    if (_mFrameBuffer != nil) {        [_mFrameBuffer bind];    }    if (_mProgram != nil) {        // 使用 GL 程序。        [_mProgram use];                // 清理窗口颜色。        glClearColor(0, 0, 0, 1);        glClear(GL_COLOR_BUFFER_BIT);        // 激活和绑定纹理单元,并设置 uniform 采样器与之对应。        glActiveTexture(GL_TEXTURE1); // 在绑定纹理之前先激活纹理单元。默认激活的纹理单元是 GL_TEXTURE0,这里激活了 GL_TEXTURE1。        glBindTexture(GL_TEXTURE_2D, frame.textureId); // 绑定这个纹理到当前激活的纹理单元 GL_TEXTURE1。        glUniform1i(_mTextureUniform, 1); // 设置 _mTextureUniform 的对应的纹理单元为 1,即 GL_TEXTURE1,从而保证每个 uniform 采样器对应着正确的纹理单元。        if (_mPostionMatrixUniform >= 0) {            glUniformMatrix4fv(_mPostionMatrixUniform, 1, false, frame.mvpMatrix.m); // 把矩阵数据发送给着色器对应的参数。        }        // 启用顶点位置属性通道。        glEnableVertexAttribArray(_mPositionAttribute);        // 启用纹理坐标属性通道。        glEnableVertexAttribArray(_mTextureCoordinateAttribute);        static const GLfloat squareVertices[] = {            -1.0f, -1.0f,            1.0f, -1.0f,            -1.0f,  1.0f,            1.0f,  1.0f,        };        // 关联顶点位置数据。        glVertexAttribPointer(_mPositionAttribute, 2, GL_FLOAT, 0, 0, squareVertices);                static GLfloat textureCoordinates[] = {            0.0f, 0.0f,            1.0f, 0.0f,            0.0f, 1.0f,            1.0f, 1.0f,        };        // 关联纹理坐标数据。        glVertexAttribPointer(_mTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);        // 绘制前回调。回调中可以更新绘制需要的相关数据。        if (self.preDrawCallBack) {            self.preDrawCallBack();        }                // 绘制所有图元。        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);                // 绘制后回调。        if (self.postDrawCallBack) {            self.postDrawCallBack();        }        // 解绑纹理。        glBindTexture(GL_TEXTURE_2D, 0);        // 关闭顶点位置属性通道。        glDisableVertexAttribArray(_mPositionAttribute);        // 关闭纹理坐标属性通道。        glDisableVertexAttribArray(_mTextureCoordinateAttribute);    }    if (_mFrameBuffer != nil) {        // 解绑内部 FBO。        [_mFrameBuffer unbind];    }    if (_mFrameBuffer != nil) {        // 清理内部 FBO。        resultFrame.textureId = _mFrameBuffer.getTextureId;        resultFrame.textureSize = _mFrameBuffer.getSize;    }        // 返回渲染好的纹理。    return resultFrame;}@end

从上面的实现代码中可以看到,KFGLFilter 的核心接口是 - (KFTextureFrame *)render:(KFTextureFrame *)frame,这个接口接受输入一帧纹理,进行渲染处理后再输出一帧纹理,这也是 KFGLFilter 的核心功能。这样一来,KFGLFilter 作为一个渲染处理节点,可以支持多个节点串起来做更复杂的处理。

KFGLFilter 提供的获取内部的 FBO、获取 GL 程序、设置 GL 程序变量值、渲染一帧纹理、渲染前回调、渲染后回调等接口则可以支持该渲染节点与外部的数据交互。

3)OpenGL 模块:KFGLProgram、KFGLFrameBuffer、KFTextureFrame、KFGLTextureAttributes

KFGLFilter 中我们还使用了 KFGLProgramKFGLFrameBufferKFTextureFrameKFGLTextureAttributes 以及一些基础定义类,他们都是对 OpenGL API 的一些封装:

  • KFGLProgram:封装了使用 GL 程序的部分 API。
  • KFGLFrameBuffer:封装了使用 FBO 的 API。
  • KFTextureFrame:表示一帧纹理对象。
  • KFFrame:表示一帧,类型可以是数据缓冲或纹理。
  • KFGLTextureAttributes:对纹理 Texture 属性的封装。
  • KFGLBase:定义了默认的 VertexShader 和 FragmentShader。
  • KFPixelBufferConvertTexture:将 CVPixelBuffer 转换为纹理 Texture 的工具类,兼容颜色空间的转换处理。

对应代码如下:

KFGLProgram.h

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN// KFGLProgram 封装了使用 GL 程序的部分 API@interface KFGLProgram : NSObject- (instancetype)initWithVertexShader:(NSString *)vertexShader fragmentShader:(NSString *)fragmentShader;- (void)use; // 使用 GL 程序- (int)getUniformLocation:(NSString *)name; // 根据名字获取 uniform 位置值- (int)getAttribLocation:(NSString *)name; // 根据名字获取 attribute 位置值@endNS_ASSUME_NONNULL_END

KFGLProgram.mm

#import "KFGLProgram.h"#import <OpenGLES/EAGL.h>#import <OpenGLES/ES2/gl.h>@interface KFGLProgram () {    int _mProgram;    int _mVertexShader;    int _mFragmentShader;}@end@implementation KFGLProgram- (instancetype)initWithVertexShader:(NSString *)vertexShader fragmentShader:(NSString *)fragmentShader {    self = [super init];    if (self) {        [self _createProgram:vertexShader fragmentSource:fragmentShader];    }    return self;}- (void)dealloc {    if (_mVertexShader != 0) {        glDeleteShader(_mVertexShader);        _mVertexShader = 0;    }    if (_mFragmentShader != 0) {        glDeleteShader(_mFragmentShader);        _mFragmentShader = 0;    }    if (_mProgram != 0) {        glDeleteProgram(_mProgram);        _mProgram = 0;    }}// 使用 GL 程序。- (void)use {    if (_mProgram != 0) {        glUseProgram(_mProgram);    }}// 根据名字获取 uniform 位置值- (int)getUniformLocation:(NSString *)name {    return glGetUniformLocation(_mProgram, [name UTF8String]);}// 根据名字获取 attribute 位置值- (int)getAttribLocation:(NSString *)name {    return glGetAttribLocation(_mProgram, [name UTF8String]);}// 加载和编译 shader,并链接 GL 程序。- (void)_createProgram:(NSString *)vertexSource fragmentSource:(NSString *)fragmentSource {    _mVertexShader = [self _loadShader:GL_VERTEX_SHADER source:vertexSource];    _mFragmentShader = [self _loadShader:GL_FRAGMENT_SHADER source:fragmentSource];    if (_mVertexShader != 0 && _mFragmentShader != 0) {        _mProgram = glCreateProgram();        glAttachShader(_mProgram, _mVertexShader);        glAttachShader(_mProgram, _mFragmentShader);        glLinkProgram(_mProgram);        GLint linkStatus;        glGetProgramiv(_mProgram, GL_LINK_STATUS, &linkStatus);        if (linkStatus != GL_TRUE) {            glDeleteProgram(_mProgram);            _mProgram = 0;        }    }}// 加载和编译 shader。- (int)_loadShader:(int)shaderType source:(NSString *)source {    int shader = glCreateShader(shaderType);    const GLchar *cSource = (GLchar *) [source UTF8String];    glShaderSource(shader,1, &cSource,NULL);    glCompileShader(shader);    GLint compiled;    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);    if (compiled != GL_TRUE) {        glDeleteShader(shader);        shader = 0;    }    return shader;}@end

KFGLFrameBuffer.h

#import <Foundation/Foundation.h>#import <UIKit/UIKit.h>#import "KFGLTextureAttributes.h"NS_ASSUME_NONNULL_BEGIN// 封装了对 FBO 使用的 API@interface KFGLFrameBuffer : NSObject- (instancetype)initWithSize:(CGSize)size;- (instancetype)initWithSize:(CGSize)size textureAttributes:(KFGLTextureAttributes *)textureAttributes;- (CGSize)getSize; // 纹理 size- (GLuint)getTextureId; // 纹理 id- (void)bind; // 绑定 FBO- (void)unbind; // 解绑 FBO@endNS_ASSUME_NONNULL_END

KFGLFrameBuffer.mm

#import "KFGLFrameBuffer.h"#import <OpenGLES/EAGL.h>#import <OpenGLES/ES2/gl.h>@interface KFGLFrameBuffer () {    GLuint _mTextureId;    GLuint _mFboId;    KFGLTextureAttributes *_mTextureAttributes;    CGSize _mSize;    int _mLastFboId;}@end@implementation KFGLFrameBuffer- (instancetype)initWithSize:(CGSize)size {    return [self initWithSize:size textureAttributes:[KFGLTextureAttributes new]];}- (instancetype)initWithSize:(CGSize)size textureAttributes:(KFGLTextureAttributes*)textureAttributes{    self = [super init];    if (self) {        _mTextureId = -1;        _mFboId = -1;        _mLastFboId = -1;        _mSize = size;        _mTextureAttributes = textureAttributes;        [self _setup];    }    return self;}- (void)dealloc {    if (_mTextureId != -1) {        glDeleteTextures(1, &_mTextureId);        _mTextureId = -1;    }    if (_mFboId != -1) {        glDeleteFramebuffers(1, &_mFboId);        _mFboId = -1;    }}- (CGSize)getSize {    return _mSize;}- (GLuint)getTextureId {    return _mTextureId;}- (void)bind {    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_mLastFboId);    if (_mFboId != -1) {        glBindFramebuffer(GL_FRAMEBUFFER, _mFboId);        glViewport(0, 0, _mSize.width, _mSize.height);    }}- (void)unbind {    glBindFramebuffer(GL_FRAMEBUFFER, _mLastFboId);}- (void)_setup {    [self _setupTexture];    [self _setupFrameBuffer];    [self _bindTexture2FrameBuffer];}-(void)_setupTexture {    if (_mTextureId == -1) {        glGenTextures(1, &_mTextureId);        glActiveTexture(GL_TEXTURE0);        glBindTexture(GL_TEXTURE_2D, _mTextureId);        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _mTextureAttributes.minFilter);        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _mTextureAttributes.magFilter);        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _mTextureAttributes.wrapS);        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _mTextureAttributes.wrapT);        if ((int)_mSize.width % 4 != 0) {            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);        }        glTexImage2D(GL_TEXTURE_2D, 0, _mTextureAttributes.internalFormat, _mSize.width, _mSize.height, 0, _mTextureAttributes.format, _mTextureAttributes.type, NULL);        glBindTexture(GL_TEXTURE_2D, 0);    }}- (void)_setupFrameBuffer {    if (_mFboId == -1) {        glGenFramebuffers(1, &_mFboId);    }} - (void)_bindTexture2FrameBuffer {    if (_mFboId != -1 && _mTextureId != -1 && _mSize.width != 0 && _mSize.height != 0) {        glBindFramebuffer(GL_FRAMEBUFFER, _mFboId);        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _mTextureId, 0);        GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);        if (status != GL_FRAMEBUFFER_COMPLETE) {            NSAssert(status == GL_FRAMEBUFFER_COMPLETE, @"Incomplete filter FBO: %d", status);        }        glBindFramebuffer(GL_FRAMEBUFFER, 0);    }}@end

KFTextureFrame.h

#import "KFFrame.h"#import <UIKit/UIKit.h>#import <CoreMedia/CoreMedia.h>#import <GLKit/GLKit.h>NS_ASSUME_NONNULL_BEGIN// 表示一帧纹理对象@interface KFTextureFrame : KFFrame@property (nonatomic, assign) CGSize textureSize;@property (nonatomic, assign) GLuint textureId;@property (nonatomic, assign) CMTime time;@property (nonatomic, assign) GLKMatrix4 mvpMatrix;- (instancetype)initWithTextureId:(GLuint)textureId textureSize:(CGSize)textureSize time:(CMTime)time;@endNS_ASSUME_NONNULL_END

KFTextureFrame.m

#import "KFTextureFrame.h"@implementation KFTextureFrame- (instancetype)initWithTextureId:(GLuint)textureId textureSize:(CGSize)textureSize time:(CMTime)time {    self = [super init];    if(self){        _textureId = textureId;        _textureSize = textureSize;        _time = time;        _mvpMatrix = GLKMatrix4Identity;    }    return self;}- (id)copyWithZone:(NSZone *)zone {    KFTextureFrame *copy = [[KFTextureFrame allocWithZone:zone] init];    copy.textureId = _textureId;    copy.textureSize = _textureSize;    copy.time = _time;    copy.mvpMatrix = _mvpMatrix;    return copy;}@end

KFFrame.h

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGINtypedef NS_ENUM(NSInteger, KFFrameType) {    KFFrameBuffer = 0, // 数据缓冲区类型    KFFrameTexture = 1, // 纹理类型};@interface KFFrame : NSObject@property (nonatomic, assign) KFFrameType frameType;- (instancetype)initWithType:(KFFrameType)type;@endNS_ASSUME_NONNULL_END

KFFrame.m

#import "KFFrame.h"@implementation KFFrame- (instancetype)initWithType:(KFFrameType)type {    self = [super init];    if(self){        _frameType = type;    }    return self;}- (instancetype)init {    self = [super init];    if(self){        _frameType = KFFrameBuffer;    }    return self;}@end

KFGLTextureAttributes.h

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN// 对纹理 Texture 属性的封装@interface KFGLTextureAttributes : NSObject@property(nonatomic, assign) int minFilter; // GL_TEXTURE_MIN_FILTER,多个纹素对应一个片元时的处理方式@property(nonatomic, assign) int magFilter; // GL_TEXTURE_MAG_FILTER,没有足够的纹素来映射片元时的处理方式@property(nonatomic, assign) int wrapS; // GL_TEXTURE_WRAP_S,超出范围的纹理处理方式,ST 坐标 S@property(nonatomic, assign) int wrapT; // GL_TEXTURE_WRAP_T,超出范围的纹理处理方式,ST 坐标 T@property(nonatomic, assign) int internalFormat;@property(nonatomic, assign) int format;@property(nonatomic, assign) int type;@endNS_ASSUME_NONNULL_END

KFGLTextureAttributes.m

#import "KFGLTextureAttributes.h"#import <OpenGLES/EAGL.h>#import <OpenGLES/ES2/gl.h>@implementation KFGLTextureAttributes- (instancetype)init {    self = [super init];    if (self) {        _minFilter = GL_LINEAR; // 混合附近纹素的颜色来计算片元的颜色。        _magFilter = GL_LINEAR; // 混合附近纹素的颜色来计算片元的颜色。        _wrapS = GL_CLAMP_TO_EDGE; // 采样纹理边缘,即剩余部分显示纹理临近的边缘颜色值。        _wrapT = GL_CLAMP_TO_EDGE; // 采样纹理边缘,即剩余部分显示纹理临近的边缘颜色值。        _internalFormat = GL_RGBA;        _format = GL_RGBA;        _type = GL_UNSIGNED_BYTE;    }        return self;}@end

KFGLBase.h

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN#define STRINGIZE(x) #x#define STRINGIZE2(x) STRINGIZE(x)#define SHADER_STRING(text) @ STRINGIZE2(text)extern NSString *const KFDefaultVertexShader;extern NSString *const KFDefaultFragmentShader;NS_ASSUME_NONNULL_END

KFGLBase.m

#import "KFGLBase.h"NSString *const KFDefaultVertexShader = SHADER_STRING(    attribute vec4 position; // 通过 attribute 通道获取顶点信息。4 维向量。    attribute vec4 inputTextureCoordinate; // 通过 attribute 通道获取纹理坐标信息。4 维向量。     varying vec2 textureCoordinate; // 用于 vertex shader 和 fragment shader 间传递纹理坐标。2 维向量。     uniform mat4 mvpMatrix; // 通过 uniform 通道获取 mvp 矩阵信息。4x4 矩阵。     void main()    {        gl_Position = mvpMatrix * position; // 根据 mvp 矩阵和顶点信息计算渲染管线最终要用的顶点信息。        textureCoordinate = inputTextureCoordinate.xy; // 将通过 attribute 通道获取的纹理坐标数据中的 2 维分量传给 fragment shader。    });NSString *const KFDefaultFragmentShader = SHADER_STRING(    varying highp vec2 textureCoordinate; // 从 vertex shader 传递来的纹理坐标。    uniform sampler2D inputImageTexture; // 通过 uniform 通道获取纹理信息。2D 纹理。     void main()    {        gl_FragColor = texture2D(inputImageTexture, textureCoordinate); // texture2D 获取指定纹理在对应坐标位置的 rgba 颜色值,作为渲染管线最终要用的颜色信息。    });

KFPixelBufferConvertTexture.h

#import <Foundation/Foundation.h>#import <OpenGLES/EAGL.h>#import <CoreVideo/CoreVideo.h>#import "KFTextureFrame.h"NS_ASSUME_NONNULL_BEGIN// KFPixelBufferConvertTexture 是一个将 CVPixelBuffer 转换为纹理 Texture 的工具类,兼容颜色空间的转换处理@interface KFPixelBufferConvertTexture : NSObject- (instancetype)initWithContext:(EAGLContext *)context;- (KFTextureFrame *)renderFrame:(CVPixelBufferRef)pixelBuffer time:(CMTime)time; // 将 CVPixelBuffer 转换为纹理 Texture@endNS_ASSUME_NONNULL_END

KFPixelBufferConvertTexture.mm

#import "KFPixelBufferConvertTexture.h"#import <OpenGLES/gltypes.h>#import "KFGLFilter.h"#import <UIKit/UIKit.h>#import <Foundation/Foundation.h>#import <CoreMedia/CoreMedia.h>#import "KFGLBase.h"static const GLfloat kFTColorConversion601VideoRange[] = {    1.164,  1.164, 1.164,    0.0, -0.392, 2.017,    1.596, -0.813,   0.0,};static const GLfloat kFTColorConversion601FullRange[] = {    1.0,    1.0,    1.0,    0.0,    -0.343, 1.765,    1.4,    -0.711, 0.0,};static const GLfloat kFTColorConversion709VideoRange[] = {    1.164,  1.164, 1.164,    0.0, -0.213, 2.112,    1.793, -0.533,   0.0,};static const GLfloat kFTColorConversion709FullRange[] = {    1.0,    1.0,    1.0,    0.0,    -0.187, 1.856,    1.575,    -0.468, 0.0,};NSString *const kFYUV2RGBShader = SHADER_STRING(    varying highp vec2 textureCoordinate;     uniform sampler2D inputImageTexture;    uniform sampler2D chrominanceTexture;    uniform mediump mat3 colorConversionMatrix;    uniform mediump int isFullRange;     void main()    {        mediump vec3 yuv;        lowp vec3 rgb;             if (isFullRange == 1) {            yuv.x = texture2D(inputImageTexture, textureCoordinate).r;        } else {            yuv.x = texture2D(inputImageTexture, textureCoordinate).r -(16.0 / 255.0);        }        yuv.yz = texture2D(chrominanceTexture, textureCoordinate).ra - vec2(0.5, 0.5);        rgb = colorConversionMatrix * yuv;             gl_FragColor = vec4(rgb, 1);    });@interface KFPixelBufferConvertTexture () {    KFGLFilter *_filter;    GLuint _chrominanceTexture;    BOOL _isFullRange;    const GLfloat *_yuvColorMatrix;    CVOpenGLESTextureCacheRef _textureCache;}@end@implementation KFPixelBufferConvertTexture- (instancetype)initWithContext:(EAGLContext *)context {    self = [super init];    if (self) {        CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, context, NULL, &_textureCache);    }    return self;}- (void)dealloc {    if (_textureCache) {        CVOpenGLESTextureCacheFlush(_textureCache, 0);        CFRelease(_textureCache);        _textureCache = NULL;    }        _filter = nil;}- (KFTextureFrame *)renderFrame:(CVPixelBufferRef)pixelBuffer time:(CMTime)time {    if (!pixelBuffer) {        return nil;    }        if (CVPixelBufferGetPlaneCount(pixelBuffer) > 0) {        return [self _yuvRenderFrame:pixelBuffer time:time];    }        return nil;}    - (void)_setupYUVProgramMatrix:(BOOL)isFullRange colorSpace:(CFTypeRef)colorSpace {    if (colorSpace == kCVImageBufferYCbCrMatrix_ITU_R_601_4) {        _yuvColorMatrix = isFullRange ? kFTColorConversion601FullRange : kFTColorConversion601VideoRange;    } else {        _yuvColorMatrix = isFullRange ? kFTColorConversion709FullRange : kFTColorConversion709VideoRange;    }    _isFullRange = isFullRange;        if (!_filter) {        _filter = [[KFGLFilter alloc] initWithCustomFBO:NO vertexShader:KFDefaultVertexShader fragmentShader:kFYUV2RGBShader];        __weak typeof(self) _self = self;        _filter.preDrawCallBack = ^() {            __strong typeof(_self) sself = _self;            if (!sself) {                return;            }            glActiveTexture(GL_TEXTURE5);            glBindTexture(GL_TEXTURE_2D, sself->_chrominanceTexture);            glUniform1i([sself->_filter.getProgram getUniformLocation:@"chrominanceTexture"], 5);                        glUniformMatrix3fv([sself->_filter.getProgram getUniformLocation:@"colorConversionMatrix"], 1, GL_FALSE, sself->_yuvColorMatrix);            glUniform1i([sself->_filter.getProgram getUniformLocation:@"isFullRange"], sself->_isFullRange ? 1 : 0);        };    }}- (BOOL)_pixelBufferIsFullRange:(CVPixelBufferRef)pixelBuffer {    // 判断 YUV 数据是否为 full range。    if (@available(iOS 15, *)) {        CFDictionaryRef cfDicAttributes = CVPixelBufferCopyCreationAttributes(pixelBuffer);        NSDictionary *dicAttributes = (__bridge_transfer NSDictionary*)cfDicAttributes;        if (dicAttributes && [dicAttributes objectForKey:@"PixelFormatDescription"]) {            NSDictionary *pixelFormatDescription = [dicAttributes objectForKey:@"PixelFormatDescription"];            if (pixelFormatDescription && [pixelFormatDescription objectForKey:(__bridge NSString*)kCVPixelFormatComponentRange]) {                NSString *componentRange = [pixelFormatDescription objectForKey:(__bridge NSString *)kCVPixelFormatComponentRange];                return [componentRange isEqualToString:(__bridge NSString *)kCVPixelFormatComponentRange_FullRange];            }        }    } else {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wdeprecated-declarations"        OSType formatType = CVPixelBufferGetPixelFormatType(pixelBuffer);        return formatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;#pragma clang diagnostic pop    }        return NO;}- (KFTextureFrame *)_yuvRenderFrame:(CVPixelBufferRef)pixelBuffer time:(CMTime)time{    BOOL isFullYUVRange = [self _pixelBufferIsFullRange:pixelBuffer];    CFTypeRef matrixKey = kCVImageBufferYCbCrMatrix_ITU_R_601_4;    if (@available(iOS 15, *)) {        matrixKey = CVBufferCopyAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, NULL);    }else{#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wdeprecated-declarations"        matrixKey = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, NULL);#pragma clang diagnostic pop    }        [self _setupYUVProgramMatrix:isFullYUVRange colorSpace:matrixKey];        CVOpenGLESTextureRef luminanceTextureRef = NULL;    CVOpenGLESTextureRef chrominanceTextureRef = NULL;        CVPixelBufferLockBaseAddress(pixelBuffer, 0);        CVReturn err;    glActiveTexture(GL_TEXTURE4);        size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);    size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);    err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCache, pixelBuffer, NULL, GL_TEXTURE_2D, GL_LUMINANCE, (GLsizei)width, (GLsizei)height, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0, &luminanceTextureRef);    if (err){        NSLog(@"KFPixelBufferConvertTexture CVOpenGLESTextureCacheCreateTextureFromImage error");        return nil;    }        GLuint luminanceTexture = CVOpenGLESTextureGetName(luminanceTextureRef);    glBindTexture(GL_TEXTURE_2D, luminanceTexture);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);        // UV-plane    glActiveTexture(GL_TEXTURE5);    size_t width_uv = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);    size_t height_uv = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);    err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCache, pixelBuffer, NULL, GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, (GLsizei)width_uv, (GLsizei)height_uv, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 1, &chrominanceTextureRef);    if (err){        NSLog(@"KFPixelBufferConvertTexture CVOpenGLESTextureCacheCreateTextureFromImage error");        return nil;    }        _chrominanceTexture = CVOpenGLESTextureGetName(chrominanceTextureRef);    glBindTexture(GL_TEXTURE_2D, _chrominanceTexture);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);        KFTextureFrame *inputFrame = [[KFTextureFrame alloc] initWithTextureId:luminanceTexture textureSize:CGSizeMake(width, height) time:time];    KFTextureFrame *resultFrame = [_filter render:inputFrame];        CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);    if(luminanceTextureRef) CFRelease(luminanceTextureRef);    if(chrominanceTextureRef) CFRelease(chrominanceTextureRef);        return resultFrame;}    @end

1.3、串联采集和渲染

最后就是在 ViewController 里将采集模块和渲染模块串起来了。代码如下:

KFVideoRenderViewController.m

#import "KFVideoRenderViewController.h"#import "KFVideoCapture.h"#import "KFPixelBufferConvertTexture.h"#import "KFOpenGLView.h"@interface KFVideoRenderViewController ()@property (nonatomic, strong) KFVideoCaptureConfig *videoCaptureConfig;@property (nonatomic, strong) KFVideoCapture *videoCapture;@property (nonatomic, strong) KFOpenGLView *glView;@property (nonatomic, strong) KFPixelBufferConvertTexture *pixelBufferConvertTexture;@property (nonatomic, strong) EAGLContext *context;@end@implementation KFVideoRenderViewController#pragma mark - Property- (KFVideoCaptureConfig *)videoCaptureConfig {    if (!_videoCaptureConfig) {        _videoCaptureConfig = [[KFVideoCaptureConfig alloc] init];    }        return _videoCaptureConfig;}- (EAGLContext *)context {    if (!_context) {        _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];    }        return _context;}- (KFPixelBufferConvertTexture *)pixelBufferConvertTexture {    if (!_pixelBufferConvertTexture) {        _pixelBufferConvertTexture = [[KFPixelBufferConvertTexture alloc] initWithContext:self.context];    }        return _pixelBufferConvertTexture;}- (KFVideoCapture *)videoCapture {    if (!_videoCapture) {        _videoCapture = [[KFVideoCapture alloc] initWithConfig:self.videoCaptureConfig];        __weak typeof(self) weakSelf = self;        _videoCapture.sampleBufferOutputCallBack = ^(CMSampleBufferRef sampleBuffer) {             // 视频采集数据回调。将采集回来的数据给渲染模块渲染。            [EAGLContext setCurrentContext:weakSelf.context];            KFTextureFrame *textureFrame = [weakSelf.pixelBufferConvertTexture renderFrame:CMSampleBufferGetImageBuffer(sampleBuffer) time:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];            [weakSelf.glView displayFrame:textureFrame];            [EAGLContext setCurrentContext:nil];        };        _videoCapture.sessionErrorCallBack = ^(NSError* error) {            NSLog(@"KFVideoCapture Error:%zi %@", error.code, error.localizedDescription);        };    }        return _videoCapture;}#pragma mark - Lifecycle- (void)viewDidLoad {    [super viewDidLoad];    [self requestAccessForVideo];    [self setupUI];}- (void)viewWillLayoutSubviews {    [super viewWillLayoutSubviews];    self.glView.frame = self.view.bounds;}#pragma mark - Action- (void)changeCamera {    [self.videoCapture changeDevicePosition:self.videoCapture.config.position == AVCaptureDevicePositionBack ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack];}#pragma mark - Private Method- (void)requestAccessForVideo {    __weak typeof(self) weakSelf = self;    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];    switch (status) {        case AVAuthorizationStatusNotDetermined:{            // 许可对话没有出现,发起授权许可。            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {                if (granted) {                    [weakSelf.videoCapture startRunning];                } else {                    // 用户拒绝。                }            }];            break;        }        case AVAuthorizationStatusAuthorized:{            // 已经开启授权,可继续。            [weakSelf.videoCapture startRunning];            break;        }        default:            break;    }}- (void)setupUI {    self.edgesForExtendedLayout = UIRectEdgeAll;    self.extendedLayoutIncludesOpaqueBars = YES;    self.title = @"Video Render";    self.view.backgroundColor = [UIColor whiteColor];        // Navigation item.    UIBarButtonItem *cameraBarButton = [[UIBarButtonItem alloc] initWithTitle:@"Camera" style:UIBarButtonItemStylePlain target:self action:@selector(changeCamera)];    self.navigationItem.rightBarButtonItems = @[cameraBarButton];        // 渲染 view。    _glView = [[KFOpenGLView alloc] initWithFrame:self.view.bounds context:self.context];    _glView.fillMode = KFGLViewContentModeFill;    [self.view addSubview:self.glView];}@end


2、Android Demo

2.1、视频采集模块

1)配置类

定义一个 KFVideoCaptureConfig 用来配置视频采集参数。实际应用的采集分辨率与相机硬件有关,一般会根据配置的分辨率查找对应最合适的分辨率。

public class KFVideoCaptureConfig {    ///< 摄像头方向    public Integer cameraFacing = CameraCharacteristics.LENS_FACING_FRONT;    ///< 分辨率    public Size resolution = new Size(1080, 1920);    ///< 帧率    public Integer fps = 30;}

2)视频采集接口和实现

视频采集接口包含以下方法:

  • 初始化
  • 开始采集
  • 停止采集
  • 切换摄像头
  • 释放实例
  • 采集状态查询
  • 获取 gl 上下文
public interface KFIVideoCapture {    ///< 视频采集初始化    public void setup(Context context, KFVideoCaptureConfig config, KFVideoCaptureListener listener, EGLContext eglShareContext);    ///< 释放采集实例    public void release();    ///< 开始采集    public void startRunning();    ///< 关闭采集    public void stopRunning();    ///< 是否正在采集    public boolean isRunning();    ///< 获取 OpenGL 上下文    public EGLContext getEGLContext();    ///< 切换摄像头    public void switchCamera();}

安卓提供了两套应用级相机框架:camera1camera2。两者区别如下:

  • camera1,最初的 camera 框架,通过 android.hardware.Camera 类提供功能接口。无安卓版本限制。
  • camera2,Android 5.0 引入的 api,通过 android.hardware.camera2 包提供功能接口。更新 camera2 的原因是 camera1 过于简单,没法满足更加复杂的相机应用场景,为了提供应用层更多控制相机的权限,才推出 camera2。安卓版本限制:requireApi >= 21

2.1)KFVideoCaptureV1

KFVideoCaptureV1:使用 camera1 的 Demo 采集实现类。实现接口 KFIVideoCapture。

包括以下模块:

  • 初始化:setup。读取相机配置;创建采集线程,在采集线程发送相机指令;创建渲染线程和 GLContext,渲染线程刷新纹理。
  • 开始采集:startRunning。初始化相机实例,配置采集参数;设置 SurfaceTexture 给 Camera: mCamera.setPreviewTexture(mSurfaceTexture.getSurfaceTexture());开启相机预览。
  • 回调采集数据:mSurfaceTextureListener。SurfaceTexture 接受 camera 采集纹理回调,在渲染线程拼装纹理数据返回给外层。
public class KFVideoCaptureV1 implements KFIVideoCapture {    public static final int KFVideoCaptureV1CameraDisableError = -3000;    private static final String TAG = "KFVideoCaptureV1";    private KFVideoCaptureListener mListener = null; ///< 回调    private KFVideoCaptureConfig mConfig = null; ///< 配置    private WeakReference<Context> mContext = null;    private boolean mCameraIsRunning = false; ///< 是否正在采集    private HandlerThread mCameraThread = null; ///< 采集线程    private Handler mCameraHandler = null;    private KFGLContext mGLContext = null; ///< GL 特效上下文    private KFSurfaceTexture mSurfaceTexture = null; ///< Surface 纹理    private KFGLFilter mOESConvert2DFilter; ///< 特效    private HandlerThread mRenderThread = null; ///< 渲染线程    private Handler mRenderHandler = null;    private Handler mMainHandler = new Handler(Looper.getMainLooper()); ///< 主线程    private Camera.CameraInfo mFrontCameraInfo = null; ///< 前置摄像头信息    private int mFrontCameraId = -1;    private Camera.CameraInfo mBackCameraInfo = null; ///< 后置摄像头信息    private int mBackCameraId = -1;    private Camera mCamera = null; ///< 当前摄像头实例(前置或者后置)    public KFVideoCaptureV1() {    }    @Override    public void setup(Context context, KFVideoCaptureConfig config, KFVideoCaptureListener listener, EGLContext eglShareContext) {        mListener = listener;        mConfig = config;        mContext = new WeakReference<Context>(context);        ///< 采集线程        mCameraThread = new HandlerThread("KFCameraThread");        mCameraThread.start();        mCameraHandler = new Handler((mCameraThread.getLooper()));        ///< 渲染线程        mRenderThread = new HandlerThread("KFCameraRenderThread");        mRenderThread.start();        mRenderHandler = new Handler((mRenderThread.getLooper()));        ///< OpenGL 上下文        mGLContext = new KFGLContext(eglShareContext);    }    @Override    public EGLContext getEGLContext() {        return mGLContext.getContext();    }    @Override    public boolean isRunning() {        return mCameraIsRunning;    }    @Override    public void release() {        mCameraHandler.post(() -> {            ///< 停止视频采集 清晰视频采集实例、OpenGL 上下文、线程等            _stopRunning();            mGLContext.bind();            if(mSurfaceTexture != null){                mSurfaceTexture.release();                mSurfaceTexture = null;            }            if(mOESConvert2DFilter != null){                mOESConvert2DFilter.release();                mOESConvert2DFilter = null;            }            mGLContext.unbind();            mGLContext.release();            mGLContext = null;            if(mCamera != null){                mCamera.release();                mCamera = null;            }            mCameraThread.quit();            mRenderThread.quit();        });    }    @Override    public void startRunning() {        mCameraHandler.post(() -> {            ///< 检测视频采集权限            if (ActivityCompat.checkSelfPermission(mContext.get(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {                ActivityCompat.requestPermissions((Activity) mContext.get(), new String[] {Manifest.permission.CAMERA}, 1);            }            ///< 检测相机是否可用            if(!_checkCameraService()){                _callBackError(KFVideoCaptureV1CameraDisableError,"相机不可用");                return;            }            ///< 开启视频采集            _startRunning();        });    }    @Override    public void stopRunning() {        mCameraHandler.post(() -> {            _stopRunning();        });    }    @Override    public void switchCamera() {        mCameraHandler.post(() -> {            ///< 切换摄像头,先关闭相机调整方向再打开相机            _stopRunning();            mConfig.cameraFacing = mConfig.cameraFacing == CameraCharacteristics.LENS_FACING_FRONT ? CameraCharacteristics.LENS_FACING_BACK : CameraCharacteristics.LENS_FACING_FRONT;            _startRunning();        });    }    private void _startRunning() {        ///< 获取前后台摄像机信息        if(mFrontCameraInfo == null || mBackCameraInfo == null){            _initCameraInfo();        }        try {            ///< 根据前后台摄像头 id 打开相机实例            mCamera = Camera.open(_getCurrentCameraId());            if(mCamera != null){                ///< 设置相机各分辨率、帧率、方向                Camera.Parameters parameters = mCamera.getParameters();                Size previewSize = _getOptimalSize(mConfig.resolution.getWidth(), mConfig.resolution.getHeight());                mConfig.resolution = new Size(previewSize.getHeight(),previewSize.getWidth());                parameters.setPreviewSize(previewSize.getWidth(),previewSize.getHeight());                Range<Integer> selectFpsRange = _chooseFpsRange();                if(selectFpsRange.getUpper() > 0) {                    parameters.setPreviewFpsRange(selectFpsRange.getLower(),selectFpsRange.getUpper());                }                mCamera.setParameters(parameters);                mCamera.setDisplayOrientation(_getDisplayOrientation());                ///< 创建 Surface 纹理                if(mSurfaceTexture == null){                    mGLContext.bind();                    mSurfaceTexture = new KFSurfaceTexture(mSurfaceTextureListener);                    mOESConvert2DFilter = new KFGLFilter(false, KFGLBase.defaultVertexShader,KFGLBase.oesFragmentShader);                    mGLContext.unbind();                }                ///< 设置 SurfaceTexture 给 Camera,这样 Camera 自动将数据渲染到 SurfaceTexture                mCamera.setPreviewTexture(mSurfaceTexture.getSurfaceTexture());                ///< 开启预览                mCamera.startPreview();                mCameraIsRunning = true;                if(mListener != null){                    mMainHandler.post(()->{                        ///< 回调相机打开                        mListener.cameraOnOpened();                    });                }            }        } catch (RuntimeException | IOException e) {            e.printStackTrace();        }    }    private void _stopRunning() {        if(mCamera != null){            ///< 关闭相机采集            mCamera.setPreviewCallback(null);            mCamera.stopPreview();            mCamera.release();            mCamera = null;            mCameraIsRunning = false;            if(mListener != null){                mMainHandler.post(()->{                    ///< 回调相机关闭                    mListener.cameraOnClosed();                });            }        }    }    private int _getCurrentCameraId() {        ///< 获取当前摄像机 id        if (mConfig.cameraFacing == CameraCharacteristics.LENS_FACING_FRONT) {            return mFrontCameraId;        } else if (mConfig.cameraFacing == CameraCharacteristics.LENS_FACING_BACK) {            return mBackCameraId;        } else {            throw new RuntimeException("No available camera id found.");        }    }    private int _getDisplayOrientation() {        ///< 获取摄像机需要旋转的方向        int orientation = 0;        if (mConfig.cameraFacing == CameraCharacteristics.LENS_FACING_FRONT) {            orientation = (_getCurrentCameraInfo().orientation) % 360;            orientation = (360 - orientation) % 360;        } else {            orientation = (_getCurrentCameraInfo().orientation + 360) % 360;        }        return orientation;    }    private Camera.CameraInfo _getCurrentCameraInfo() {        ///< 获取当前摄像机描述信息        if (mConfig.cameraFacing == CameraCharacteristics.LENS_FACING_FRONT) {            return mFrontCameraInfo;        } else if (mConfig.cameraFacing == CameraCharacteristics.LENS_FACING_BACK) {            return mBackCameraInfo;        } else {            throw new RuntimeException("No available camera id found.");        }    }    private Size _getOptimalSize(int width, int height) {        ///< 根据外层输入分辨率查找对应最合适的分辨率        List<Camera.Size> sizeMap = mCamera.getParameters().getSupportedPreviewSizes();        List<Size> sizeList = new ArrayList<>();        for (Camera.Size option:sizeMap) {            if (width > height) {                if (option.width >= width && option.height >= height) {                    sizeList.add(new Size(option.width,option.height));                }            } else {                if (option.width >= height && option.height >= width) {                    sizeList.add(new Size(option.width,option.height));                }            }        }        if (sizeList.size() > 0) {            return Collections.min(sizeList, new Comparator<Size>() {                @Override                public int compare(Size o1, Size o2) {                    return Long.signum(o1.getWidth() * o1.getHeight() - o2.getWidth() * o2.getHeight());                }            });        }        return new Size(0,0);    }    private Range<Integer> _chooseFpsRange() {        ///< 根据外层设置帧率查找最合适的帧率        List<int[]> fpsRange = mCamera.getParameters().getSupportedPreviewFpsRange();        for(int[] range : fpsRange){            if(range.length == 2 && range[1] >= mConfig.fps*1000 && range[0] <= mConfig.fps*1000){                // return new Range<>(range[0],mConfig.fps*1000);                return new Range<>(range[0],range[1]); ///< 仅支持列表中一项,不能像 camera2 一样指定            }        }        return new Range<Integer>(0,0);    }    private void _initCameraInfo() {        ///< 获取前置后置摄像头描述信息与 id        int numberOfCameras = Camera.getNumberOfCameras();        for (int cameraId = 0; cameraId < numberOfCameras; cameraId++) {            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();            Camera.getCameraInfo(cameraId, cameraInfo);            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {                // 后置摄像头信息                mBackCameraId = cameraId;                mBackCameraInfo = cameraInfo;            } else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {                // 前置摄像头信息                mFrontCameraId = cameraId;                mFrontCameraInfo = cameraInfo;            }        }    }    private boolean _checkCameraService(){        ///< 检测相机是否可用        DevicePolicyManager dpm = (DevicePolicyManager)mContext.get().getSystemService(Context.DEVICE_POLICY_SERVICE);        if (dpm.getCameraDisabled(null)) {            return false;        }        return true;    }    private void _callBackError(int error, String errorMsg){        ///< 错误回调        if(mListener != null){            mMainHandler.post(()->{                mListener.cameraOnError(error,TAG + errorMsg);            });        }    }    private KFSurfaceTextureListener mSurfaceTextureListener = new KFSurfaceTextureListener() {        @Override        ///< SurfaceTexture 数据回调        public void onFrameAvailable(SurfaceTexture surfaceTexture) {            mRenderHandler.post(()->{                long timestamp = System.nanoTime();                mGLContext.bind();                ///< 刷新纹理数据至 SurfaceTexture                mSurfaceTexture.getSurfaceTexture().updateTexImage();                if(mListener != null){                    ///< 拼装好纹理数据返回给外层                    KFTextureFrame frame = new KFTextureFrame(mSurfaceTexture.getSurfaceTextureId(),mConfig.resolution,timestamp,true);                    mSurfaceTexture.getSurfaceTexture().getTransformMatrix(frame.textureMatrix);                    KFFrame convertFrame = mOESConvert2DFilter.render(frame);                    mListener.onFrameAvailable(convertFrame);                }                mGLContext.unbind();            });        }    };}

3.2)KFVideoCaptureV1

KFVideoCaptureV2:使用 camera2 的 demo 采集实现类。

实现接口 KFIVideoCapture。模块设计和 KFVideoCaptureV1 基本一致,只是 camera api 调用有差异,不再赘述。

public class KFVideoCaptureV2 implements KFIVideoCapture {    public static final int KFVideoCaptureV2CameraDisableError = -3000;    private static final String TAG = "KFVideoCaptureV2";    private KFVideoCaptureListener mListener = null; ///< 回调    private KFVideoCaptureConfig mConfig = null; ///< 采集配置    private WeakReference<Context> mContext = null;    private CameraManager mCameraManager = null; ///< 相机系统服务,用于管理和连接相机设备    private String mCameraId; ///<摄像头id    private CameraDevice mCameraDevice = null; ///< 相机设备类    private HandlerThread mCameraThread = null; ///< 采集线程    private Handler mCameraHandler = null;    private CaptureRequest.Builder mCaptureRequestBuilder = null; ///< CaptureRequest 的构造器,使用 Builder 模式,设置更加方便    private CaptureRequest mCaptureRequest = null; ///< 相机捕获图像的设置请求,包含传感器、镜头、闪光灯等    private CameraCaptureSession mCameraCaptureSession = null; ///< 请求抓取相机图像帧的会话,会话的建立主要会建立起一个通道,源端是相机,另一端是 Target    private boolean mCameraIsRunning = false;    private Range<Integer>[] mFpsRange;    private KFGLContext mGLContext = null;    private KFSurfaceTexture mSurfaceTexture = null;    private KFGLFilter mOESConvert2DFilter; ///< 特效    private Surface mSurface = null;    private HandlerThread mRenderThread = null;    private Handler mRenderHandler = null;    private Handler mMainHandler = new Handler(Looper.getMainLooper());    public KFVideoCaptureV2() {    }    @Override    public void setup(Context context, KFVideoCaptureConfig config, KFVideoCaptureListener listener, EGLContext eglShareContext) {        mListener = listener;        mConfig = config;        mContext = new WeakReference<Context>(context);        ///< 相机采集线程        mCameraThread = new HandlerThread("KFCameraThread");        mCameraThread.start();        mCameraHandler = new Handler((mCameraThread.getLooper()));        ///< 渲染线程        mRenderThread = new HandlerThread("KFCameraRenderThread");        mRenderThread.start();        mRenderHandler = new Handler((mRenderThread.getLooper()));        mGLContext = new KFGLContext(eglShareContext);    }    @Override    public EGLContext getEGLContext() {        return mGLContext.getContext();    }    @Override    public boolean isRunning() {        return mCameraIsRunning;    }    @Override    public void startRunning() {        ///< 开启预览        mCameraHandler.post(() -> {            _startRunning();        });    }    @Override    public void stopRunning() {        ///< 停止预览        mCameraHandler.post(() -> {            _stopRunning();        });    }    @Override    public void release() {        mCameraHandler.post(() -> {            ///< 关闭采集、释放 SurfaceTexture、OpenGL 上下文、线程等            _stopRunning();            mGLContext.bind();            if(mSurfaceTexture != null){                mSurfaceTexture.release();                mSurfaceTexture = null;            }            if(mOESConvert2DFilter != null){                mOESConvert2DFilter.release();                mOESConvert2DFilter = null;            }            mGLContext.unbind();            mGLContext.release();            mGLContext = null;            if(mSurface != null){                mSurface.release();                mSurface = null;            }            mCameraThread.quit();            mRenderThread.quit();        });    }    @Override    public void switchCamera() {        ///< 切换摄像头        mCameraHandler.post(() -> {            _stopRunning();            mConfig.cameraFacing = mConfig.cameraFacing == CameraCharacteristics.LENS_FACING_FRONT ? CameraCharacteristics.LENS_FACING_BACK : CameraCharacteristics.LENS_FACING_FRONT;            _startRunning();        });    }    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)    private void _startRunning() {        ///< 获取相机系统服务        if(mCameraManager == null){            mCameraManager = (CameraManager) mContext.get().getSystemService(Context.CAMERA_SERVICE);        }        ///< 根据外层摄像头方向查找摄像头 id        boolean selectSuccess = _chooseCamera();        if (selectSuccess) {            try {                ///< 检测采集权限                if (ActivityCompat.checkSelfPermission(mContext.get(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {                    ActivityCompat.requestPermissions((Activity) mContext.get(), new String[] {Manifest.permission.CAMERA}, 1);                }                ///< 检测相机是否可用                if(!_checkCameraService()){                    _callBackError(KFVideoCaptureV2CameraDisableError, "相机不可用");                    return;                }                ///< 打开相机设备                mCameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);            } catch (CameraAccessException e) {                e.printStackTrace();            }        }    }    private void _stopRunning() {        ///< 停止采集        if(mCameraCaptureSession != null) {            mCameraCaptureSession.close();            mCameraCaptureSession = null;        }        if(mCameraDevice != null){            mCameraDevice.close();            mCameraDevice = null;        }    }    private KFSurfaceTextureListener mSurfaceTextureListener = new KFSurfaceTextureListener() {        @Override        //< SurfaceTexture 数据回调        public void onFrameAvailable(SurfaceTexture surfaceTexture) {            mRenderHandler.post(() -> {                long timestamp = System.nanoTime();                mGLContext.bind();                ///< 刷新纹理数据至 SurfaceTexture                mSurfaceTexture.getSurfaceTexture().updateTexImage();                if(mListener != null){                    ///< 拼装好纹理数据返回给外层                    KFTextureFrame frame = new KFTextureFrame(mSurfaceTexture.getSurfaceTextureId(),mConfig.resolution,timestamp,true);                    mSurfaceTexture.getSurfaceTexture().getTransformMatrix(frame.textureMatrix);                    KFFrame convertFrame = mOESConvert2DFilter.render(frame);                    mListener.onFrameAvailable(convertFrame);                }                mGLContext.unbind();            });        }    };    private CameraCaptureSession.StateCallback mCaputreSessionCallback = new CameraCaptureSession.StateCallback() {        @Override        ///< 创建会话回调        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {            ///< 创建CaptureRequest            mCaptureRequest = mCaptureRequestBuilder.build();            mCameraCaptureSession = cameraCaptureSession;            try {                ///< 通过连续重复的 Capture 实现预览功能,每次 Capture 会把预览画面显示到对应的 Surface 上                mCameraCaptureSession.setRepeatingRequest(mCaptureRequest, null, null);            } catch (CameraAccessException e) {                e.printStackTrace();            }        }        @Override        ///< 创建会话出错回调        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {            _callBackError(1005,"onConfigureFailed");        }    };    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {        @Override        ///< 相机打开回调        public void onOpened(@NonNull CameraDevice camera) {            mCameraDevice = camera;            try {                ///< 通过相机设备创建构造器                mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);                Range<Integer> selectFpsRange = _chooseFpsRange();                ///< 设置帧率                if(selectFpsRange.getUpper() > 0){                    mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,selectFpsRange);                }            } catch (CameraAccessException e) {                e.printStackTrace();            }            if(mListener != null){                mMainHandler.post(()->{                    mListener.cameraOnOpened();                });            }            mCameraIsRunning = true;            if(mSurfaceTexture == null){                mGLContext.bind();                mSurfaceTexture = new KFSurfaceTexture(mSurfaceTextureListener);                mOESConvert2DFilter = new KFGLFilter(false, KFGLBase.defaultVertexShader,KFGLBase.oesFragmentShader);                mGLContext.unbind();                mSurface = new Surface(mSurfaceTexture.getSurfaceTexture());            }            if(mSurface != null) {                ///< 设置目标输出 Surface                mSurfaceTexture.getSurfaceTexture().setDefaultBufferSize(mConfig.resolution.getHeight(),mConfig.resolution.getWidth());                mCaptureRequestBuilder.addTarget(mSurface);                try {                    ///< 创建通道会话                    mCameraDevice.createCaptureSession(Arrays.asList(mSurface), mCaputreSessionCallback, mCameraHandler);                } catch (CameraAccessException e) {                    e.printStackTrace();                }            }        }        @Override        public void onDisconnected(@NonNull CameraDevice camera) {            ///< 相机断开连接回调            camera.close();            mCameraDevice = null;            mCameraIsRunning = false;        }        @Override        public void onClosed(@NonNull CameraDevice camera) {            ///< 相机关闭回调            camera.close();            mCameraDevice = null;            if(mListener != null){                mMainHandler.post(()->{                    mListener.cameraOnClosed();                });            }            mCameraIsRunning = false;        }        @Override        public void onError(@NonNull CameraDevice camera, int error) {            ///< 相机出错回调            camera.close();            mCameraDevice = null;            _callBackError(error,"Camera onError");            mCameraIsRunning = false;        }    };    private boolean _chooseCamera() {        try {            ///< 根据外层配置方向选择合适的设备 id 与 FPS 区间            final String[] ids = mCameraManager.getCameraIdList();            for(String cameraId : ids) {                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);                if(facing == mConfig.cameraFacing){                    mCameraId = cameraId;                    mFpsRange = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);                    StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);                    if (map != null) {                        Size previewSize = _getOptimalSize(map.getOutputSizes(SurfaceTexture.class), mConfig.resolution.getWidth(), mConfig.resolution.getHeight());                        // Range<Integer>[] fpsRanges = map.getHighSpeedVideoFpsRangesFor(previewSize); ///< high fps range                        mConfig.resolution = new Size(previewSize.getHeight(),previewSize.getWidth());                    }                    return true;                }            }        } catch (CameraAccessException e) {            e.printStackTrace();        }        return false;    }    private Size _getOptimalSize(Size[] sizeMap, int width, int height) {        ///< 根据外层配置分辨率寻找合适的分辨率        List<Size> sizeList = new ArrayList<>();        for (Size option : sizeMap) {            if (width > height) {                if (option.getWidth() >= width && option.getHeight() >= height) {                    sizeList.add(option);                }            } else {                if (option.getWidth() >= height && option.getHeight() >= width) {                    sizeList.add(option);                }            }        }        if (sizeList.size() > 0) {            return Collections.min(sizeList, new Comparator<Size>() {                @Override                public int compare(Size o1, Size o2) {                    return Long.signum(o1.getWidth() * o1.getHeight() - o2.getWidth() * o2.getHeight());                }            });        }        return sizeMap[0];    }    private boolean _checkCameraService(){        ///< 检测相机是否可用        DevicePolicyManager dpm = (DevicePolicyManager)mContext.get().getSystemService(Context.DEVICE_POLICY_SERVICE);        if (dpm.getCameraDisabled(null)) {            return false;        }        return true;    }    private void _callBackError(int error, String errorMsg){        ///< 错误回调        if(mListener != null){            mMainHandler.post(()->{                mListener.cameraOnError(error,TAG + errorMsg);            });        }    }    private Range<Integer> _chooseFpsRange() {        ///< 根据外层配置的帧率寻找合适的帧率        for(Range<Integer> range : mFpsRange){            if(range.getUpper() >= mConfig.fps && range.getLower() <= mConfig.fps){                return new Range<>(range.getLower(),mConfig.fps);            }        }        return new Range<Integer>(0,0);    }}

2.2、视频渲染模块

1)KFGLContext

负责创建 OpenGL 环境,实现与 RenderDemo(1):用 OpenGL 画一个三角形 中一样,此处不再重复介绍。

2)KFGLFilter

KFGLFilter 是一个自定义滤镜,外部输入纹理,进行自定义效果渲染。

绘制流程和绘制三角形一致:加载编译 shader,链接到 shader 程序,设置顶点数据,绘制三角形。

Demo 中的 shader 只是最简单的纹理绘制,可以修改 shader 实现相机滤镜、美颜等效果。

public class KFGLFilter {    private boolean mIsCustomFBO = false; // 是否自定义帧缓存 部分渲染到指定 Surface 等其它场景会自定义    private KFGLFrameBuffer mFrameBuffer = null; // 帧缓存    private KFGLProgram mProgram = null; // 着色器容器    private KFGLTextureAttributes mGLTextureAttributes = null; // 纹理格式描述    private int mTextureUniform = -1; // 纹理下标    private int mPostionMatrixUniform = -1; // 顶点矩阵下标    private int mTextureMatrixUniform = -1; // 纹理矩阵下标    private int mPositionAttribute = -1; // 顶点下标    private int mTextureCoordinateAttribute = -1; // 纹理下标    private FloatBuffer mSquareVerticesBuffer = null; // 顶点 buffer    private FloatBuffer mTextureCoordinatesBuffer = null; // 纹理 buffer    private FloatBuffer mCustomSquareVerticesBuffer = null; // 自定义顶点 buffer    private FloatBuffer mCustomTextureCoordinatesBuffer = null; // 自定义纹理 buffer    public KFGLFilter(boolean isCustomFBO,String vertexShader,String fragmentShader) {        mIsCustomFBO = isCustomFBO;        // 初始化着色器        _setupProgram(vertexShader,fragmentShader);    }    public KFGLFilter(boolean isCustomFBO,String vertexShader,String fragmentShader, KFGLTextureAttributes textureAttributes) {        mIsCustomFBO = isCustomFBO;        mGLTextureAttributes = textureAttributes;        // 初始化着色器        _setupProgram(vertexShader,fragmentShader);    }    public KFGLFrameBuffer getOutputFrameBuffer() {        return mFrameBuffer;    }    public KFFrame render(KFTextureFrame frame){        if(frame == null){            return frame;        }        KFTextureFrame resultFrame = new KFTextureFrame(frame);        // 初始化帧缓存        _setupFrameBuffer(frame.textureSize);        // 绑定帧缓存        if(mFrameBuffer != null){            mFrameBuffer.bind();        }        if(mProgram != null){            // 使用着色器            mProgram.use();            // 设置帧缓存背景色            glClearColor(0,0,0,1);            // 清空帧缓存颜色            glClear(GLES20.GL_COLOR_BUFFER_BIT);            // 激活纹理单元 1            glActiveTexture(GLES20.GL_TEXTURE1);            // 根据是否 OES 纹理绑定纹理 id            if (frame.isOESTexture) {                glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, frame.textureId);            } else {                glBindTexture(GLES20.GL_TEXTURE_2D, frame.textureId);            }            // 传递纹理单元 1            glUniform1i(mTextureUniform, 1);            // 设置纹理矩阵            if(mTextureMatrixUniform >= 0){                glUniformMatrix4fv(mTextureMatrixUniform, 1, false, frame.textureMatrix, 0);            }            // 设置顶点矩阵            if(mPostionMatrixUniform >= 0){                glUniformMatrix4fv(mPostionMatrixUniform, 1, false, frame.positionMatrix, 0);            }            // 启用顶点着色器顶点坐标属性            glEnableVertexAttribArray(mPositionAttribute);            // 启用顶点着色器纹理坐标属性            glEnableVertexAttribArray(mTextureCoordinateAttribute);            // 根据自定义顶点缓存设置不同顶点坐标            if(mCustomSquareVerticesBuffer != null){                mCustomSquareVerticesBuffer.position(0);                glVertexAttribPointer(mPositionAttribute, 2, GLES20.GL_FLOAT, false, 0, mCustomSquareVerticesBuffer);            }else{                mSquareVerticesBuffer.position(0);                glVertexAttribPointer(mPositionAttribute, 2, GLES20.GL_FLOAT, false, 0, mSquareVerticesBuffer);            }            // 根据自定义纹理缓存设置不同纹理坐标            if(mCustomTextureCoordinatesBuffer != null){                mCustomTextureCoordinatesBuffer.position(0);                glVertexAttribPointer(mTextureCoordinateAttribute, 2, GLES20.GL_FLOAT, false, 0, mCustomTextureCoordinatesBuffer);            }else{                mTextureCoordinatesBuffer.position(0);                glVertexAttribPointer(mTextureCoordinateAttribute, 2, GLES20.GL_FLOAT, false, 0, mTextureCoordinatesBuffer);            }            // 真正的渲染            glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);            // 解除绑定纹理            if (frame.isOESTexture) {                glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);            } else {                glBindTexture(GLES20.GL_TEXTURE_2D, 0);            }            // 关闭顶点着色器顶点属性            glDisableVertexAttribArray(mPositionAttribute);            // 关闭顶点着色器纹理属性            glDisableVertexAttribArray(mTextureCoordinateAttribute);        }        // 解绑帧缓存        if(mFrameBuffer != null){            mFrameBuffer.unbind();        }        // 返回渲染后数据        if(mFrameBuffer != null){            resultFrame.textureId = mFrameBuffer.getTextureId();            resultFrame.textureSize = mFrameBuffer.getSize();            resultFrame.isOESTexture = false;            resultFrame.textureMatrix = KFGLBase.KFIdentityMatrix();            resultFrame.positionMatrix = KFGLBase.KFIdentityMatrix();        }        return resultFrame;    }    public void release() {        // 释放帧缓存、着色器        if(mFrameBuffer != null){            mFrameBuffer.release();            mFrameBuffer = null;        }        if(mProgram != null){            mProgram.release();            mProgram = null;        }    }    public void setSquareVerticesBuffer(FloatBuffer squareVerticesBuffer) {        mSquareVerticesBuffer = squareVerticesBuffer;    }    public void setTextureCoordinatesBuffer(FloatBuffer textureCoordinatesBuffer) {        mCustomTextureCoordinatesBuffer = textureCoordinatesBuffer;    }    public void setIntegerUniformValue(String uniformName, int intValue){        // 设置 int 类型 uniform 数据        if(mProgram != null){            int uniforamIndex = mProgram.getUniformLocation(uniformName);            mProgram.use();            glUniform1i(uniforamIndex, intValue);        }    }    public void setFloatUniformValue(String uniformName, float floatValue){        // 设置 float 类型 uniform 数据        if(mProgram != null){            int uniforamIndex = mProgram.getUniformLocation(uniformName);            mProgram.use();            glUniform1f(uniforamIndex, floatValue);        }    }    private void _setupFrameBuffer(Size size) {        if(mIsCustomFBO) {            return;        }        // 初始化帧缓存与对应纹理        if(mFrameBuffer == null || mFrameBuffer.getSize().getWidth() != size.getWidth() || mFrameBuffer.getSize().getHeight() != size.getHeight()){            if(mFrameBuffer != null){                mFrameBuffer.release();                mFrameBuffer = null;            }            mFrameBuffer = new KFGLFrameBuffer(size,mGLTextureAttributes);        }    }    private void _setupProgram(String vertexShader,String fragmentShader){        // 根据 vs fs 初始化着色器容器        if(mProgram == null){            mProgram = new KFGLProgram(vertexShader,fragmentShader);            mTextureUniform = mProgram.getUniformLocation("inputImageTexture");            mPostionMatrixUniform = mProgram.getUniformLocation("mvpMatrix");            mTextureMatrixUniform = mProgram.getUniformLocation("textureMatrix");            mPositionAttribute = mProgram.getAttribLocation("position");            mTextureCoordinateAttribute = mProgram.getAttribLocation("inputTextureCoordinate");            final float squareVertices[] = {                    -1.0f, -1.0f,                    1.0f, -1.0f,                    -1.0f,  1.0f,                    1.0f,  1.0f,            };            ByteBuffer squareVerticesByteBuffer = ByteBuffer.allocateDirect(4 * squareVertices.length);            squareVerticesByteBuffer.order(ByteOrder.nativeOrder());            mSquareVerticesBuffer = squareVerticesByteBuffer.asFloatBuffer();            mSquareVerticesBuffer.put(squareVertices);            mSquareVerticesBuffer.position(0);            final float textureCoordinates[] = {                    0.0f, 0.0f,                    1.0f, 0.0f,                    0.0f, 1.0f,                    1.0f, 1.0f,            };            ByteBuffer textureCoordinatesByteBuffer = ByteBuffer.allocateDirect(4 * textureCoordinates.length);            textureCoordinatesByteBuffer.order(ByteOrder.nativeOrder());            mTextureCoordinatesBuffer = textureCoordinatesByteBuffer.asFloatBuffer();            mTextureCoordinatesBuffer.put(textureCoordinates);            mTextureCoordinatesBuffer.position(0);        }    }}

3)KFRenderView

KFRenderView 是渲染模块,实现如下:

  • 初始化渲染视图:可选 TextureView 或 SurfaceView 作为实际的渲染视图,添加视图到父布局。
  • 渲染:在相机采集纹理的回调里,承接外部输入纹理给 KFGLFilter,渲染到 View 的 Surface 上。
  • 销毁:释放 GL 上下文,释放渲染时的帧缓存、着色器。
public class KFRenderView extends ViewGroup {    private KFGLContext mEGLContext = null; // OpenGL上下文    private KFGLFilter mFilter = null; // 特效渲染到指定 Surface    private EGLContext mShareContext = null; // 共享上下文    private View mRenderView = null; // 渲染视图基类    private int mSurfaceWidth = 0; // 渲染缓存宽    private int mSurfaceHeight = 0; // 渲染缓存高    private FloatBuffer mSquareVerticesBuffer = null; // 自定义顶点    private KFRenderMode mRenderMode = KFRenderMode.KFRenderModeFill; // 自适应模式 黑边 比例填冲    private boolean mSurfaceChanged = false; // 渲染缓存是否变更    private Size mLastRenderSize = new Size(0,0); // 标记上次渲染 Size    public enum KFRenderMode {        KFRenderStretch,// 拉伸满-可能变形        KFRenderModeFit,// 黑边        KFRenderModeFill// 比例填充    };    public KFRenderView(Context context, EGLContext eglContext){        super(context);        mShareContext = eglContext; // 共享上下文        _setupSquareVertices(); // 初始化顶点        boolean isSurfaceView  = false; // TextureView 与 SurfaceView 开关        if(isSurfaceView){            mRenderView = new KFSurfaceView(context, mListener);        }else{            mRenderView = new KFTextureView(context, mListener);        }        this.addView(mRenderView); // 添加视图到父视图    }    public void release() {        // 释放 GL 上下文、特效        if(mEGLContext != null){            mEGLContext.bind();            if(mFilter != null){                mFilter.release();                mFilter = null;            }            mEGLContext.unbind();            mEGLContext.release();            mEGLContext = null;        }    }    public void render(KFTextureFrame inputFrame){        if(inputFrame == null){            return;        }        //输入纹理使用自定义特效渲染到 View 的 Surface 上        if(mEGLContext != null && mFilter != null){            boolean frameResolutionChanged = inputFrame.textureSize.getWidth() != mLastRenderSize.getWidth() || inputFrame.textureSize.getHeight() != mLastRenderSize.getHeight();            // 渲染缓存变更或者视图大小变更重新设置顶点            if(mSurfaceChanged || frameResolutionChanged){                _recalculateVertices(inputFrame.textureSize);                mSurfaceChanged = false;                mLastRenderSize = inputFrame.textureSize;            }            // 渲染到指定 Surface            mEGLContext.bind();            mFilter.setSquareVerticesBuffer(mSquareVerticesBuffer);            GLES20.glViewport(0, 0, mSurfaceWidth, mSurfaceHeight);            mFilter.render(inputFrame);            mEGLContext.swapBuffers();            mEGLContext.unbind();        }    }    private KFRenderListener mListener = new KFRenderListener() {        @Override        // 渲染缓存创建        public void surfaceCreate(@NonNull Surface surface) {            mEGLContext = new KFGLContext(mShareContext,surface);            // 初始化特效            mEGLContext.bind();            _setupFilter();            mEGLContext.unbind();        }        @Override        // 渲染缓存变更        public void surfaceChanged(@NonNull Surface surface, int width, int height) {            mSurfaceWidth = width;            mSurfaceHeight = height;            mSurfaceChanged = true;            // 设置 GL 上下文 Surface            mEGLContext.bind();            mEGLContext.setSurface(surface);            mEGLContext.unbind();        }        @Override        public void surfaceDestroy(@NonNull Surface surface) {        }    };    private void _setupFilter() {        // 初始化特效        if(mFilter == null){            mFilter = new KFGLFilter(true, KFGLBase.defaultVertexShader,KFGLBase.defaultFragmentShader);        }    }    private void _setupSquareVertices() {        // 初始化顶点缓存        final float squareVertices[] = {                -1.0f, -1.0f,                1.0f, -1.0f,                -1.0f,  1.0f,                1.0f,  1.0f,        };        ByteBuffer squareVerticesByteBuffer = ByteBuffer.allocateDirect(4 * squareVertices.length);        squareVerticesByteBuffer.order(ByteOrder.nativeOrder());        mSquareVerticesBuffer = squareVerticesByteBuffer.asFloatBuffer();        mSquareVerticesBuffer.put(squareVertices);        mSquareVerticesBuffer.position(0);    }    private void _recalculateVertices(Size inputImageSize){        // 按照适应模式创建顶点        if(mSurfaceWidth == 0 || mSurfaceHeight == 0){            return;        }        Size renderSize = new Size(mSurfaceWidth,mSurfaceHeight);        float heightScaling = 1, widthScaling = 1;        Size insetSize = new Size(0,0);        float inputAspectRatio = (float) inputImageSize.getWidth() / (float)inputImageSize.getHeight();        float outputAspectRatio = (float)renderSize.getWidth() / (float)renderSize.getHeight();        boolean isAutomaticHeight = inputAspectRatio <= outputAspectRatio ? false : true;        if (isAutomaticHeight) {            float insetSizeHeight = (float)inputImageSize.getHeight() / ((float)inputImageSize.getWidth() / (float)renderSize.getWidth());            insetSize = new Size(renderSize.getWidth(),(int)insetSizeHeight);        } else {            float insetSizeWidth = (float)inputImageSize.getWidth() / ((float)inputImageSize.getHeight() / (float)renderSize.getHeight());            insetSize = new Size((int)insetSizeWidth,renderSize.getHeight());        }        switch (mRenderMode) {            case KFRenderStretch: {                widthScaling = 1;                heightScaling = 1;            }; break;            case KFRenderModeFit: {                widthScaling = (float)insetSize.getWidth() / (float)renderSize.getWidth();                heightScaling = (float)insetSize.getHeight() / (float)renderSize.getHeight();            }; break;            case KFRenderModeFill: {                widthScaling = (float) renderSize.getHeight() / (float)insetSize.getHeight();                heightScaling = (float)renderSize.getWidth() / (float)insetSize.getWidth();            }; break;        }        final float squareVertices[] = {                -1.0f, -1.0f,                1.0f, -1.0f,                -1.0f,  1.0f,                1.0f,  1.0f,        };        final float customVertices[] = {                -widthScaling, -heightScaling,                widthScaling, -heightScaling,                -widthScaling,  heightScaling,                widthScaling,  heightScaling,        };        ByteBuffer squareVerticesByteBuffer = ByteBuffer.allocateDirect(4 * customVertices.length);        squareVerticesByteBuffer.order(ByteOrder.nativeOrder());        mSquareVerticesBuffer = squareVerticesByteBuffer.asFloatBuffer();        mSquareVerticesBuffer.put(customVertices);        mSquareVerticesBuffer.position(0);    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        // 视图变更 Size        this.mRenderView.layout(left,top,right,bottom);    }}

2.3、串联采集和渲染

MainActivity 中串联采集和渲染模块,实现相机图像实时预览功能。包括如下过程:

  • 初始化采集事例:创建 KFIVideoCapture 实例并启动采集。
  • 初始化渲染视图:创建 KFRenderView 并添加到 Demo 视图。
  • 采集数据回调给渲染:KFIVideoCapture 注册监听 KFVideoCaptureListener,每帧采集触发回调 onFrameAvailable(frame),将回调数据输入 KFRenderView 驱动渲染,实现实时预览。
public class MainActivity extends AppCompatActivity {    private KFIVideoCapture mCapture;    private KFVideoCaptureConfig mCaptureConfig;    private KFRenderView mRenderView;    private KFGLContext mGLContext;    private Button cameraButton;    private Button playerButton;    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||                ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||                ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {            ActivityCompat.requestPermissions((Activity) this,                    new String[] {Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},                    1);        }        playerButton = findViewById(R.id.player_btn);        cameraButton = findViewById(R.id.camera_btn);        playerButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {            }        });        mGLContext = new KFGLContext(null);        mRenderView = new KFRenderView(this,mGLContext.getContext());        WindowManager windowManager = (WindowManager)this.getSystemService(this.WINDOW_SERVICE);        Rect outRect = new Rect();        windowManager.getDefaultDisplay().getRectSize(outRect);        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(outRect.width(), outRect.height());        addContentView(mRenderView,params);        mCaptureConfig = new KFVideoCaptureConfig();        mCaptureConfig.cameraFacing = LENS_FACING_FRONT;        mCaptureConfig.resolution = new Size(720,1280);        mCaptureConfig.fps = 30;        boolean useCamera2 = false;        if(useCamera2){            mCapture = new KFVideoCaptureV2();        }else{            mCapture = new KFVideoCaptureV1();        }        mCapture.setup(this,mCaptureConfig,mVideoCaptureListener,mGLContext.getContext());        mCapture.startRunning();    }    private KFVideoCaptureListener mVideoCaptureListener = new KFVideoCaptureListener() {        @Override        public void cameraOnOpened(){}        @Override        public void cameraOnClosed() {        }        @Override        public void cameraOnError(int error,String errorMsg) {        }        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)        @Override        public void onFrameAvailable(KFFrame frame) {            mRenderView.render((KFTextureFrame) frame);        }    };}

相关代码就介绍到这里。

版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除

本文地址:http://0561fc.cn/209440.html