iOS視覺-- (10) OpenGL ES+GLSL實現(xiàn)YUV視頻渲染解析

本文借鑒:落影大神--iOS開發(fā)-OpenGL ES實踐教程(一)

本文Demo
前面一篇我們學(xué)習(xí)了OpenGL ES渲染攝像頭錄制的視頻幀的知識窿冯,就是將照相機(jī)錄制的CMSampleBuffer轉(zhuǎn)換成CVOpenGLESTexture紋理的再進(jìn)行渲染的過程幕庐。如下圖:

CMSampleBuffer轉(zhuǎn)紋理

這篇呢我們將要學(xué)習(xí)使用OpenGL ES渲染YUV視頻幀,原理和渲染照相機(jī)是一樣的愿卸。重點在于視頻幀的獲取佳晶。這里我使用兩種方式進(jìn)行視頻幀的獲取桅狠。這里由于沒有錄制視頻時的回調(diào)方法,我們可以使用一個定時器來代替轿秧,這里使用 CADisplayLink

        displayLink = CADisplayLink(target: self, selector: #selector(displayLinkDidUpdate(_:)))
        displayLink.add(to: RunLoop.current, forMode: RunLoop.Mode.default)
        displayLink.preferredFramesPerSecond = 30
        displayLink.isPaused = true
  • 1. 使用 AVPlayerItemVideoOutput 方式獲戎械:
        let videoURL = URL(fileURLWithPath: Bundle.main.path(forResource: "test.mov", ofType: nil)!)
        self.reader = DDAssetReader(videoURL)
        
        let item = AVPlayerItem(url: videoURL)
        player = AVPlayer(playerItem: item)
        let asset: AVAsset = item.asset
        asset.loadValuesAsynchronously(forKeys: ["tracks"]) {
            if asset.statusOfValue(forKey: "tracks", error: nil) == AVKeyValueStatus.loaded {
                let tracks = asset.tracks(withMediaType: AVMediaType.video)
                if tracks.count > 0 {
                    // Choose the first video track.
                    let videoTrack: AVAssetTrack = tracks.first!
                    videoTrack.loadValuesAsynchronously(forKeys: ["preferredTransform"]) {
                        if videoTrack.statusOfValue(forKey: "preferredTransform", error: nil) == AVKeyValueStatus.loaded {
                            let preferredTransform: CGAffineTransform = videoTrack.preferredTransform
                            let preferredRotation = -1 * atan2(preferredTransform.b, preferredTransform.a)
                            NSLog("preferredRotation ----> \(preferredRotation)")

                            DispatchQueue.main.async {
                                item.add(self.videoOutput)
                                self.player.replaceCurrentItem(with: item)
                                self.videoOutput.requestNotificationOfMediaDataChange(withAdvanceInterval: 0.03)
                                self.player.play()
                            }
                        }
                    }
                }
            }
        }

        player.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none
        NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item, queue: OperationQueue.main) { noti in
            self.player.currentItem?.seek(to: CMTime.zero, completionHandler: { suc in

            })
        }

        mProcessQueue = DispatchQueue(label: "mProcessQueue")

        //kCVPixelFormatType_32BGRA
        videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: [String(kCVPixelBufferPixelFormatTypeKey) : kCVPixelFormatType_420YpCbCr8BiPlanarFullRange])
        videoOutput.setDelegate(self, queue: mProcessQueue)

然后通過CADisplayLink的回調(diào)方法來獲取視頻幀:

    @objc func displayLinkDidUpdate(_ sender: CADisplayLink) {
        var outputItemTime: CMTime = .invalid

        // Calculate the nextVsync time which is when the screen will be refreshed next.
        let nextVSync: CFTimeInterval = sender.timestamp + sender.duration
        outputItemTime = videoOutput.itemTime(forHostTime: nextVSync)

        if videoOutput.hasNewPixelBuffer(forItemTime: outputItemTime) {
            var pixelBuffer: CVPixelBuffer?
            pixelBuffer = videoOutput.copyPixelBuffer(forItemTime: outputItemTime, itemTimeForDisplay: nil)
            self.renderView.renderBuffer(pixelBuffer: pixelBuffer)
        }
    }

  • 2. 使用 AVAssetReader + AVAssetReaderTrackOutput 方式獲取:
class DDAssetReader: NSObject {

    var readerVideoTrackOutput: AVAssetReaderTrackOutput!
    var assetReader: AVAssetReader!
    var videoUrl: URL!
    var lock: NSLock!
    
    init(_ url: URL) {
        super.init()
        videoUrl = url
        lock = NSLock()
        customInit()
    }
    
    func customInit() {
        let inputOptions = [AVURLAssetPreferPreciseDurationAndTimingKey : true]
        let inputAsset = AVURLAsset(url: videoUrl, options: inputOptions)
        inputAsset.loadValuesAsynchronously(forKeys: ["tracks"]) {
            DispatchQueue.global().async {
                var error: NSError?
                let tracksStatus = inputAsset.statusOfValue(forKey: "tracks", error: &error)
                if (tracksStatus != AVKeyValueStatus.loaded) {
                    NSLog("error = \(error!)")
                    return
                }
                self.processWithAsset(inputAsset)
            }
        }
    }
    
    func processWithAsset(_ asset: AVAsset) {
        lock.lock()
        NSLog("processWithAsset")

        assetReader = try? AVAssetReader(asset: asset)
        
        let outputSettings = [String(kCVPixelBufferPixelFormatTypeKey) : kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]
        
        readerVideoTrackOutput = AVAssetReaderTrackOutput(track: asset.tracks(withMediaType: AVMediaType.video).first!, outputSettings: outputSettings)
        readerVideoTrackOutput.alwaysCopiesSampleData = false
        assetReader.add(readerVideoTrackOutput)

        
        if (assetReader.startReading() == false) {
            NSLog("Error reading from file at URL: %@", asset)
        }
        lock.unlock()
    }
    
    func readBuffer() -> CMSampleBuffer? {
        lock.lock()
        var sampleBuffer: CMSampleBuffer?
        
        if ((readerVideoTrackOutput) != nil) {
            sampleBuffer = readerVideoTrackOutput.copyNextSampleBuffer()
        }
        
        if ((assetReader != nil) && assetReader.status == AVAssetReader.Status.completed) {
            NSLog("customInit")
            readerVideoTrackOutput = nil
            assetReader = nil
            customInit()
        }
        
        lock.unlock()
        return sampleBuffer
    }
}

本文Demo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末菇篡,一起剝皮案震驚了整個濱河市漩符,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驱还,老刑警劉巖嗜暴,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異议蟆,居然都是意外死亡闷沥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門咐容,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舆逃,“玉大人,你說我怎么就攤上這事疟丙∮敝叮” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵享郊,是天一觀的道長览祖。 經(jīng)常有香客問我,道長炊琉,這世上最難降的妖魔是什么展蒂? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任又活,我火速辦了婚禮,結(jié)果婚禮上锰悼,老公的妹妹穿的比我還像新娘柳骄。我一直安慰自己,他們只是感情好箕般,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布耐薯。 她就那樣靜靜地躺著,像睡著了一般丝里。 火紅的嫁衣襯著肌膚如雪曲初。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天杯聚,我揣著相機(jī)與錄音臼婆,去河邊找鬼。 笑死幌绍,一個胖子當(dāng)著我的面吹牛颁褂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播傀广,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颁独,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伪冰?” 一聲冷哼從身側(cè)響起奖唯,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糜值,沒想到半個月后丰捷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡寂汇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年病往,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骄瓣。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡停巷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出榕栏,到底是詐尸還是另有隱情畔勤,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布扒磁,位于F島的核電站庆揪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏妨托。R本人自食惡果不足惜缸榛,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一吝羞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧内颗,春花似錦钧排、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至找前,卻和暖如春筒捺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纸厉。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留五嫂,地道東北人颗品。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像沃缘,于是被迫代替她去往敵國和親躯枢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容