iOS MP4轉(zhuǎn)CGImageRef數(shù)組循環(huán)播放(AVAssetReader+AVAssetReaderTrackOutput)

開始實(shí)現(xiàn)之前,先介紹一下 AVFoundation用到的類!

  1. AVAsset
    一個(gè)統(tǒng)一多媒體文件類,不局限于音頻視頻,我們就可以通過這個(gè)類獲取到它的多媒體文件各種屬性比如類型時(shí)長每秒幀數(shù)等等

  2. AVURLAsset
    AVAsset子類,用來本地或者遠(yuǎn)程創(chuàng)建AVAsset

  3. AVAssetTrack
    多媒體文件軌道:AVMediaTypeVideo/AVMediaTypeAudio/AVMediaTypeText等等
    一般來說視頻有兩個(gè)軌道,一個(gè)是播放聲音一個(gè)播放畫面,所以說我們需要取播放畫面的軌道的話:

 self.assetTrack = self.asset?.tracksWithMediaType(AVMediaTypeVideo)[0]
  1. AVAssetReader
    我們可以通過這個(gè)類獲取asset的媒體數(shù)據(jù)(會(huì)拋出異常,所以放在do-catch里面或者直接try!)
self.assetReader = try AVAssetReader(asset: self.asset!)
  1. AVAssetReaderTrackOutput
    能從AVAssetReader對(duì)象中讀取同一類型媒體數(shù)據(jù)的樣品的集合,大概就是視頻輸出的意思,從AVAssetTrack獲取到某一通道的多媒體文件,然后通過AVAssetReader.startReading()方法開始獲取視頻的每一幀。
    關(guān)于AVAssetReaderTrackOutput采樣輸出屬性:
let m_pixelFormatType = kCVPixelFormatType_32BGRA //iOS在內(nèi)部進(jìn)行YUV至BGRA格式轉(zhuǎn)換
outputSettings:[String(kCVPixelBufferPixelFormatTypeKey) : Int(m_pixelFormatType)]

這個(gè)直接使用網(wǎng)上的這個(gè),但是看到stackoverflow有說,除非需要特別的format,不然可以outputSettings為nil,說是AVFoundation會(huì)選擇最優(yōu)效率的format
Query for optimal pixel format when capturing video on iOS?

  1. while循環(huán)處理視頻幀樣本
assetReader?.startReading()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    // 確保大于0幀
    while self.assetReader?.status == .Reading  && self.assetTrack?.nominalFrameRate > 0 {
        // 讀取視頻
        autoreleasepool({
            let videoBuffer = self.videoReaderOutput?.copyNextSampleBuffer()
            if videoBuffer != nil {
                let cgImage = self.cgImageFromSampleBufferRef(videoBuffer!)
                guard self.delegate != nil else {
                    print("代理沒設(shè)置")
                    return
                }
                self.delegate?.movieDecoderCallBack(self, cgImage: cgImage.takeRetainedValue())
                //                        cgImage.release()
                // 根據(jù)需要休眠一段時(shí)間;比如上層播放視頻時(shí)每幀之間是有間隔的
                NSThread.sleepForTimeInterval(0.001)
            }
        })
    }
    // 完成回調(diào)
    guard self.delegate != nil else {
        print("代理沒設(shè)置")
        return
    }
    self.delegate?.movieDecoderFinishCallBack(self)
}
  1. CMSampleBuffer--> CGImageRef的方法我是在Objective-C里面處理的,因?yàn)閟wift的autoreleasepool里面不能return
-(CGImageRef)cgImageFromSampleBufferRef:(CMSampleBufferRef)sampleBufferRef {
     @autoreleasepool {
        // 為媒體數(shù)據(jù)設(shè)置一個(gè)CMSampleBufferRef
        CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBufferRef);
        // 鎖定 pixel buffer 的基地址,保證在內(nèi)存中可用
        CVPixelBufferLockBaseAddress(imageBuffer, 0);
        // 得到 pixel buffer 的基地址
        void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
        // 得到 pixel buffer 的行字節(jié)數(shù)
        size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
        // 得到 pixel buffer 的寬和高
        size_t width = CVPixelBufferGetWidth(imageBuffer);
        size_t height = CVPixelBufferGetHeight(imageBuffer);
        // 創(chuàng)建一個(gè)依賴于設(shè)備的 RGB 顏色空間
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        // 用抽樣緩存的數(shù)據(jù)創(chuàng)建一個(gè)位圖格式的圖形上下文(graphic context)對(duì)象
        CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
        //根據(jù)這個(gè)位圖 context 中的像素創(chuàng)建一個(gè) Quartz image 對(duì)象
        CGImageRef quartzImage = CGBitmapContextCreateImage(context);
        // 解鎖 pixel buffer
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
        // CVPixelBufferRelease(imageBuffer);
        // Free up the context and color space
        CGContextRelease(context);
        CGColorSpaceRelease(colorSpace);
        return quartzImage;
     }
}
  1. CMSampleBufferRef
    AVAssetReaderTrackOutput.copyNextSampleBuffer()能同步copy下一個(gè)CMSampleBuffer。每一個(gè)CMSampleBuffer在緩沖區(qū)有一個(gè)單獨(dú)的樣本幀(視頻樣本幀)相關(guān)聯(lián)。

  2. CVImageBufferRef
    有了CMSampleBuffer,我們就可以在圖像緩沖區(qū)創(chuàng)建CVImageBufferRef(Pixel buffers 像素緩沖區(qū)屬于CVImageBufferRef派生類),有了這個(gè)圖像緩沖區(qū),我們就可以對(duì)它做像素級(jí)別的操作。

  3. CGBitmapContextCreate創(chuàng)建位圖對(duì)象

  • data: 指向要渲染的繪制內(nèi)存的地址。這個(gè)內(nèi)存塊的大小至少是(bytesPerRow*height)個(gè)字節(jié)
  • width: bitmap的寬度,單位為像素
  • height: bitmap的高度,單位為像素
  • bitsPerComponent: 內(nèi)存中像素的每個(gè)組件的位數(shù).例如,對(duì)于32位像素格式和RGB 顏色空間,你應(yīng)該將這個(gè)值設(shè)為8.
  • bytesPerRow: bitmap的每一行在內(nèi)存所占的比特?cái)?shù)
  • colorspace: bitmap上下文使用的顏色空間。
  • bitmapInfo: 指定bitmap是否包含alpha通道,像素中alpha通道的相對(duì)位置,像素組件是整形還是浮點(diǎn)型等信息的字符串。

第六步和第七步都使用了autoreleasepool為了及時(shí)釋放掉內(nèi)存,網(wǎng)上看到很多網(wǎng)友說用這個(gè)方法都內(nèi)存爆掉了,我加了這兩個(gè)之后雖然還是在Leaks看到有內(nèi)存泄露,但是沒有出現(xiàn)過內(nèi)存爆掉的情況

    @nonobjc private var images: Array<CGImageRef> = []
    var animation: CAKeyframeAnimation?

    override func prepareForReuse() {
        animation = nil
        images.removeAll()
        videoView.layer.removeAllAnimations()
    }

    func movieDecoderFinishCallBack(movieDecoder: MovieDecoder) {
        dispatch_async(dispatch_get_main_queue()) {    
            let videoPath = "\(CacheDirectory)/\(self.cellModel.mp4Id).mp4"
            let fileUrl = NSURL(fileURLWithPath: videoPath)
            let asset = AVURLAsset(URL: fileUrl)
            self.animation = CAKeyframeAnimation.init(keyPath: "contents")
            self.animation!.duration = Double(asset.duration.value)/Double(asset.duration.timescale);
            self.animation!.values = self.images;
            self.animation!.repeatCount = MAXFLOAT;
            self.startAnimation()
            // 清除緩存
            self.images.removeAll()
        }
    }

除了在movieDecoderFinishCallBack回調(diào)執(zhí)行self.images.removeAll()之外,我還在prepareForReuse方法里面將animation置成nil,然后發(fā)現(xiàn)無論怎么滾動(dòng),內(nèi)存都穩(wěn)定在50M以下,比以前動(dòng)輒幾百M(fèi)的好多了,不會(huì)出現(xiàn)crash的情況,不過依然會(huì)出現(xiàn)內(nèi)存泄露的問題。


最后在網(wǎng)上找到一個(gè)感覺像是處理內(nèi)存泄露的方法,具體我沒考究,不過應(yīng)該可以提供些許思路:
把一個(gè)視頻拆分成多個(gè)AVAssetTrack,這樣做的原因是因?yàn)椋褂肁VAssetReader讀取每一幀SampleBuffer的數(shù)據(jù)是需要把數(shù)據(jù)加載到內(nèi)存里面去的,如果直接把整個(gè)視頻的SampleBuffer加載到內(nèi)存,會(huì)造成閃退
鏈接:GitHub:KayWong/VideoReverse 使用AVFoundation實(shí)現(xiàn)視頻倒序

以上代碼參考鏈接:
IOS 微信聊天發(fā)送小視頻的秘密(AVAssetReader+AVAssetReaderTrackOutput播放視頻)

國慶期間我會(huì)寫一個(gè)demo出來,現(xiàn)在暫時(shí)沒時(shí)間。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • --繪圖與濾鏡全面解析 概述 在iOS中可以很容易的開發(fā)出絢麗的界面效果,一方面得益于成功系統(tǒng)的設(shè)計(jì),另一方面得益...
    韓七夏閱讀 2,970評(píng)論 2 10
  • 本篇文章是基于谷歌有關(guān)Graphic的一篇概覽文章的翻譯:http://source.android.com/de...
    lee_3do閱讀 7,445評(píng)論 2 21
  • 轉(zhuǎn)載請(qǐng)帶上出處, 謝謝. 一個(gè) Graphics Context 代表一個(gè)繪制目標(biāo), 它包含繪制系統(tǒng)用于完成繪制指...
    Falme丶閱讀 1,877評(píng)論 0 2
  • 難得在夜深人靜之時(shí)心也是靜的,無執(zhí)于早起,便莫名的在臺(tái)燈前思考起這個(gè)問題來。 “人是什么”是哲學(xué)、宗教、科學(xué)都關(guān)注...
    一只學(xué)術(shù)小狐貍閱讀 1,240評(píng)論 0 3
  • 真的有好久好久不寫東西了啊。最近比較懶惰。有趣的事情發(fā)生了不少,但就是懶得記下來,明明并不是沒有時(shí)間~ ...
    畫樓西閱讀 233評(píng)論 0 1

友情鏈接更多精彩內(nèi)容