使用Metal/OpenGL ES在iOS上渲染视频

6

如何使用Metal或OpenGL ES渲染视频?

这里说的是自己解码显示

我对MetalOpenGL ES非常新手,不知道从哪里开始。

2个回答

15
您所询问的对于刚开始接触此事的人来说并不是一件简单的事情,因此建议您将其分解成更小的部分。 话虽如此,我已经完成了此操作,并可以描述一般过程。
首先,您将使用AVAssetReader,并为音频和视频轨道设置AVAssetReaderOutputs。从中,您会迭代CMSampleBufferRefs。对于每个视频帧,您将提取一个CVImageBufferRef。
将视频帧上传到OpenGL纹理可以采用几种不同的方法。最高效的路径是使用YUV数据,并通过iOS的纹理缓存上传Y和UV平面。然后,您将使用YUV-> RGB着色器将这些平面组合并转换为RGB颜色空间以进行处理或显示。
您还可以使用电影文件中的BGRA数据,并直接将其上传到纹理中,但该过程存在更多开销。 不过,它更简单,并避免了手动颜色转换着色器的需要。
之后,您将采用旁路顶点着色器和片段着色器或者利用着色器对视频进行处理,将纹理渲染到四边形上。
上传到Metal的类似过程和路径存在,并且起始点相同。
现在,如果您不想手动实现所有这些内容,我编写了一个名为GPUImage的开源框架来封装它。它有Objective-CSwift版本。如果您不想下载整个框架,请关注GPUImageMovie(前者)或MovieInput(后者)类。它们包含执行此操作所需的所有代码,因此您可以从中提取实现并直接使用它们。

2
非常感谢您。要想得出一个概念证明,我可能需要几个星期的时间。 - user7404038
你好,Brad Larson。你的项目能用于显示来自RTSP的多个流吗? - Lysdexia

4
以下是最简单、最快速的入门方法:
@import UIKit;
@import AVFoundation;
@import CoreMedia;
#import <MetalKit/MetalKit.h>
#import <Metal/Metal.h>
#import <MetalPerformanceShaders/MetalPerformanceShaders.h>

@interface ViewController : UIViewController <MTKViewDelegate, AVCaptureVideoDataOutputSampleBufferDelegate>  {
    NSString *_displayName;
    NSString *serviceType;
}

@property (retain, nonatomic) SessionContainer *session;
@property (retain, nonatomic) AVCaptureSession *avSession;

@end;

#import "ViewController.h"

@interface ViewController () {
    MTKView *_metalView;

    id<MTLDevice> _device;
    id<MTLCommandQueue> _commandQueue;
    id<MTLTexture> _texture;

    CVMetalTextureCacheRef _textureCache;
}

@property (strong, nonatomic) AVCaptureDevice *videoDevice;
@property (nonatomic) dispatch_queue_t sessionQueue;

@end

@implementation ViewController

- (void)viewDidLoad {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    [super viewDidLoad];

    _device = MTLCreateSystemDefaultDevice();
    _metalView = [[MTKView alloc] initWithFrame:self.view.bounds];
    [_metalView setContentMode:UIViewContentModeScaleAspectFit];
    _metalView.device = _device;
    _metalView.delegate = self;
    _metalView.clearColor = MTLClearColorMake(1, 1, 1, 1);
    _metalView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
    _metalView.framebufferOnly = NO;
    _metalView.autoResizeDrawable = NO;

    CVMetalTextureCacheCreate(NULL, NULL, _device, NULL, &_textureCache);

    [self.view addSubview:_metalView];

    self.sessionQueue = dispatch_queue_create( "session queue", DISPATCH_QUEUE_SERIAL );

    if ([self setupCamera]) {
        [_avSession startRunning];
    }
}

- (BOOL)setupCamera {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    @try {
        NSError * error;

            _avSession = [[AVCaptureSession alloc] init];
            [_avSession beginConfiguration];
            [_avSession setSessionPreset:AVCaptureSessionPreset640x480];

            // get list of devices; connect to front-facing camera
            self.videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
            if (self.videoDevice == nil) return FALSE;

            AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:self.videoDevice error:&error];
            [_avSession addInput:input];

            dispatch_queue_t sampleBufferQueue = dispatch_queue_create("CameraMulticaster", DISPATCH_QUEUE_SERIAL);

            AVCaptureVideoDataOutput * dataOutput = [[AVCaptureVideoDataOutput alloc] init];
            [dataOutput setAlwaysDiscardsLateVideoFrames:YES];
            [dataOutput setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)}];
            [dataOutput setSampleBufferDelegate:self queue:sampleBufferQueue];

            [_avSession addOutput:dataOutput];
            [_avSession commitConfiguration]; 
    } @catch (NSException *exception) {
        NSLog(@"%s - %@", __PRETTY_FUNCTION__, exception.description);
        return FALSE;
    } @finally {
        return TRUE;
    }

}

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    {
        size_t width = CVPixelBufferGetWidth(pixelBuffer);
        size_t height = CVPixelBufferGetHeight(pixelBuffer);

        CVMetalTextureRef texture = NULL;
        CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCache, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &texture);
        if(status == kCVReturnSuccess)
        {
            _metalView.drawableSize = CGSizeMake(width, height);
            _texture = CVMetalTextureGetTexture(texture);
            _commandQueue = [_device newCommandQueue];
            CFRelease(texture);
        }
    }
}

- (void)drawInMTKView:(MTKView *)view {
    // creating command encoder
    if (_texture) {
        id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
        id<MTLTexture> drawingTexture = view.currentDrawable.texture;

        // set up and encode the filter
        MPSImageGaussianBlur *filter = [[MPSImageGaussianBlur alloc] initWithDevice:_device sigma:5];

        [filter encodeToCommandBuffer:commandBuffer sourceTexture:_texture destinationTexture:drawingTexture];

        // committing the drawing
        [commandBuffer presentDrawable:view.currentDrawable];
        [commandBuffer commit];
        _texture = nil;
    }
}

- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {

}

@end

Opengles 3与Metal,在这种类型的处理中性能差异有多大(如果有)? - Deepak Sharma
那个问题在2015年WWDC演示中得到了回答,“Metal的新功能,第一部分”,您可以在以下链接观看:https://developer.apple.com/videos/play/wwdc2015/603/ - James Bush
我已经尝试了这些代码。但是我发现在“[commandBuffer commit]”几秒钟后它会崩溃。我该怎么处理? - user2027712

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接