GPUImage3(二)

上一篇学习了Base里面的几个文件。今天我学习一下OutPuts里面的几个文件。


image.png

1. RenderView

首先先来看下能将纹理展示到手机屏幕上的文件RenderView

public class RenderView: MTKView, ImageConsumer {
    
    public let sources = SourceContainer()
    public let maximumInputs: UInt = 1
    var currentTexture: Texture? ///当前纹理
    var renderPipelineState:MTLRenderPipelineState! ///渲染管线
    
    public override init(frame frameRect: CGRect, device: MTLDevice?) {
        super.init(frame: frameRect, device: sharedMetalRenderingDevice.device)
        
        commonInit()
    }
    
    public required init(coder: NSCoder) {
        super.init(coder: coder)
        
        commonInit()
    }
    
    private func commonInit() {
        framebufferOnly = false
        autoResizeDrawable = true
        
        self.device = sharedMetalRenderingDevice.device
        
        let (pipelineState, _) = generateRenderPipelineState(device:sharedMetalRenderingDevice, vertexFunctionName:"oneInputVertex", fragmentFunctionName:"passthroughFragment", operationName:"RenderView")
        self.renderPipelineState = pipelineState
        
        enableSetNeedsDisplay = false
        isPaused = true
    }
    
    public func newTextureAvailable(_ texture:Texture, fromSourceIndex:UInt) {
        self.drawableSize = CGSize(width: texture.texture.width, height: texture.texture.height)
        currentTexture = texture
        self.draw()
    }
    
    public override func draw(_ rect:CGRect) {
        if let currentDrawable = self.currentDrawable, let imageTexture = currentTexture {
            let commandBuffer = sharedMetalRenderingDevice.commandQueue.makeCommandBuffer()
            
            let outputTexture = Texture(orientation: .portrait, texture: currentDrawable.texture)
            commandBuffer?.renderQuad(pipelineState: renderPipelineState, inputTextures: [0:imageTexture], outputTexture: outputTexture)
            
            commandBuffer?.present(currentDrawable)
            commandBuffer?.commit()
        }
    }
}

这就是他里面全部的代码,很少,只有几十行代码

RenderView继承与MTKView,并遵循ImageConsumer,这就说明了只接受纹理数据
很同意理解,通过newTextureAvailable()接受纹理,调用draw ()将纹理绘制到视图

这里就讨论下里面设置的几个属性的问题:
framebufferOnly = false:如果值为true(默认值),则该类仅使用用法标志分配其对象。然后,Core Animation可以优化纹理以进行显示。但是,您可能无法采样,读取或写入这些纹理。要支持采样和像素读/写操作(以性能为代价),请将此值设置为false
autoResizeDrawable:如果值为true,则在调整视图大小时,视图会自动调整其基础颜色,深度,模板和多重采样纹理的大小。如果值为false,则必须显式设置以更改这些对象的大小。(drawablesize)默认值为true
enableSetNeedsDisplay:则视图的行为类似于UIView,响应对setNeedsDisplay的调用。 将视图标记为要显示后,该视图会在应用程序的事件循环中的每次遍历中自动重新显示。 将enableSetNeedsDisplay设置为true还将暂停MTKView的内部渲染循环,而更新将由事件驱动。 默认值为false
isPaused: 如果为true,则委托将基于内部计时器以preferredFramesPerSecond的速率接收drawInMTKView消息,或者子类将接收drawRect消息。 默认值为false

MTKView 是MetalKit的一个类,layer 是 CAMetalLayer,负责渲染内容到屏幕。

驱动模式
提供三种渲染模式。分别由两个变量控制。

  • 默认模式,paused 和 enableSetNeedsDisplay 都是NO,渲染由内部的定时器驱动
  • paused 和 enableSetNeedsDisplay 都是YES,由view的渲染通知驱动,比如调用setNeedsDisplay
  • paused 是 YES, enableSetNeedsDisplay 是 NO, 这个由主动调用MTKView 的draw方法

渲染方法

  • 子类MTKView,在drawRect:方法里实现
  • 设置MTKView的代理,在代理drawInMTKView:方法实现

文字来自于:https://blog.csdn.net/weixin_34010949/article/details/91370893

2.PictureOutput

可以导出纹理成图片,并且写入指定路径中。
主要就2个方法:

/// 设置保存路径,图片保存格式
    public func saveNextFrameToURL(_ url:URL, format:PictureFileFormat) {
        onlyCaptureNextFrame = true
        encodedImageFormat = format
        self.url = url // Create an intentional short-term retain cycle to prevent deallocation before next frame is captured
        /// 编码成功回调 将数据写入指定路径中
        encodedImageAvailableCallback = {imageData in
            do {
                try imageData.write(to: self.url, options:.atomic)
            } catch {
                // TODO: Handle this better
                print("WARNING: Couldn't save image with error:\(error)")
            }
        }
    }
    
  /// 接受新纹理
    public func newTextureAvailable(_ texture:Texture, fromSourceIndex:UInt) {
      ...
      
        if let imageCallback = encodedImageAvailableCallback {
            let cgImageFromBytes = texture.cgImage()
            
            let imageData:Data

            let image = UIImage(cgImage:cgImageFromBytes, scale:1.0, orientation:.up)
            switch encodedImageFormat {
            case .png: imageData = image.pngData()! // TODO: Better error handling here
                case .jpeg: imageData = image.jpegData(compressionQuality: 0.8)! // TODO: Be able to set image quality
            }
            imageCallback(imageData)
            
            if onlyCaptureNextFrame {
                encodedImageAvailableCallback = nil
            }
        }
    }

其中主要的就是纹理转换成CGImage的方法,在Texture对象内部,方法如下:

extension Texture {
    func cgImage() -> CGImage {
        // Flip and swizzle image
        //翻转和旋转图像
        guard let commandBuffer = sharedMetalRenderingDevice.commandQueue.makeCommandBuffer() else { fatalError("Could not create command buffer on image rendering.")}
        let outputTexture = Texture(device:sharedMetalRenderingDevice.device, orientation:self.orientation, width:self.texture.width, height:self.texture.height)
        commandBuffer.renderQuad(pipelineState:sharedMetalRenderingDevice.colorSwizzleRenderState, uniformSettings:nil, inputTextures:[0:self], useNormalizedTextureCoordinates:true, outputTexture:outputTexture)
        commandBuffer.commit()
        /// 阻止当前线程的执行,直到命令缓冲区的执行完成
        commandBuffer.waitUntilCompleted()
        
        // Grab texture bytes, generate CGImageRef from them
        /// 图片大小
        let imageByteSize = texture.height * texture.width * 4
        /// 分配空间大小
        let outputBytes = UnsafeMutablePointer<UInt8>.allocate(capacity:imageByteSize)
        /// 获取纹理数据保存到 outputBytes
        outputTexture.texture.getBytes(outputBytes, bytesPerRow: MemoryLayout<UInt8>.size * texture.width * 4, bytesPerImage:0, from: MTLRegionMake2D(0, 0, texture.width, texture.height), mipmapLevel: 0, slice: 0)
        
        guard let dataProvider = CGDataProvider(dataInfo:nil, data:outputBytes, size:imageByteSize, releaseData:dataProviderReleaseCallback) else {fatalError("Could not create CGDataProvider")}
        let defaultRGBColorSpace = CGColorSpaceCreateDeviceRGB()
        return CGImage(width:texture.width,   //图片宽
                       height:texture.height, //图片高
                       bitsPerComponent:8, //位数
                       bitsPerPixel:32,   // 像素所占位数
                       bytesPerRow:4 * texture.width, //没一行的字节数
                       space:defaultRGBColorSpace,
                       bitmapInfo:CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue),
                       provider:dataProvider,
                       decode:nil,
                       shouldInterpolate:false,
                       intent:.defaultIntent)!
    }
}

MTLTexture通过getBytes()方法拿到数据,再通过CGImage绘制出来.
这样我们就拿到了纹理的图片数据了,最后想写入或者显示等操作都可以

3.MovieOutput

将持续获取到的纹理数据写入成视频文件到指定路径中。
我们直接来看核心的方法

    public func newTextureAvailable(_ texture:Texture, fromSourceIndex:UInt) {
        ...

        /// 申请创建一个空的CVPixelBuffer
        var pixelBufferFromPool:CVPixelBuffer? = nil
        let pixelBufferStatus = CVPixelBufferPoolCreatePixelBuffer(nil, assetWriterPixelBufferInput.pixelBufferPool!, &pixelBufferFromPool)
        guard let pixelBuffer = pixelBufferFromPool, (pixelBufferStatus == kCVReturnSuccess) else { return }
        
        CVPixelBufferLockBaseAddress(pixelBuffer, [])
        /// 将纹理数据填充到CVPixelBuffer
        renderIntoPixelBuffer(pixelBuffer, texture:texture)
        
        if (!assetWriterPixelBufferInput.append(pixelBuffer, withPresentationTime:frameTime)) {
            print("Problem appending pixel buffer at time: \(frameTime)")
        }
        
        CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
    }
    
    /// 将纹理数据填充到CVPixelBuffer
    func renderIntoPixelBuffer(_ pixelBuffer:CVPixelBuffer, texture:Texture) {
        /// 获取pixelBuffer像素的基本地址
        guard let pixelBufferBytes = CVPixelBufferGetBaseAddress(pixelBuffer) else {
            print("Could not get buffer bytes")
            return
        }
        /// 获取一行的字节数
        let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
        
        let outputTexture:Texture
        if (Int(round(self.size.width)) != texture.texture.width) && (Int(round(self.size.height)) != texture.texture.height) {
            let commandBuffer = sharedMetalRenderingDevice.commandQueue.makeCommandBuffer()
            
            outputTexture = Texture(device:sharedMetalRenderingDevice.device, orientation: .portrait, width: Int(round(self.size.width)), height: Int(round(self.size.height)), timingStyle: texture.timingStyle)

            commandBuffer?.renderQuad(pipelineState: renderPipelineState, inputTextures: [0:texture], outputTexture: outputTexture)
            commandBuffer?.commit()
            commandBuffer?.waitUntilCompleted()
        } else {
            outputTexture = texture
        }
        
        let region = MTLRegionMake2D(0, 0, outputTexture.texture.width, outputTexture.texture.height)
        
        /// 将数据填充到pixelBufferBytes中
        outputTexture.texture.getBytes(pixelBufferBytes, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)
    }

步骤:1.接受到新的纹理
2.申请创建一个新的CVPixelBuffer
3.获取CVPixelBuffer像素的基本地址,将纹理数据写入CVPixelBuffer中
4.将有数据的CVPixelBuffer添加到assetWriterPixelBufferInput中

注:调用CVPixelBufferGetBaseAddress()之前必须调用 CVPixelBufferLockBaseAddress()锁定pixelbuffer

在此Outputs就完了。仅以此勉励自己。

热爱生活,记录生活!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352