iOS MP4转CGImageRef数组循环播放(AVAssetReader+AVAssetReaderTrackOutput)

开始实现之前,先介绍一下 AVFoundation用到的类!

  1. AVAsset
    一个统一多媒体文件类,不局限于音频视频,我们就可以通过这个类获取到它的多媒体文件各种属性比如类型时长每秒帧数等等

  2. AVURLAsset
    AVAsset子类,用来本地或者远程创建AVAsset

  3. AVAssetTrack
    多媒体文件轨道:AVMediaTypeVideo/AVMediaTypeAudio/AVMediaTypeText等等
    一般来说视频有两个轨道,一个是播放声音一个播放画面,所以说我们需要取播放画面的轨道的话:

 self.assetTrack = self.asset?.tracksWithMediaType(AVMediaTypeVideo)[0]
  1. AVAssetReader
    我们可以通过这个类获取asset的媒体数据(会抛出异常,所以放在do-catch里面或者直接try!)
self.assetReader = try AVAssetReader(asset: self.asset!)
  1. AVAssetReaderTrackOutput
    能从AVAssetReader对象中读取同一类型媒体数据的样品的集合,大概就是视频输出的意思,从AVAssetTrack获取到某一通道的多媒体文件,然后通过AVAssetReader.startReading()方法开始获取视频的每一帧。
    关于AVAssetReaderTrackOutput采样输出属性:
let m_pixelFormatType = kCVPixelFormatType_32BGRA //iOS在内部进行YUV至BGRA格式转换
outputSettings:[String(kCVPixelBufferPixelFormatTypeKey) : Int(m_pixelFormatType)]

这个直接使用网上的这个,但是看到stackoverflow有说,除非需要特别的format,不然可以outputSettings为nil,说是AVFoundation会选择最优效率的format
Query for optimal pixel format when capturing video on iOS?

  1. while循环处理视频帧样本
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("代理没设置")
                    return
                }
                self.delegate?.movieDecoderCallBack(self, cgImage: cgImage.takeRetainedValue())
                //                        cgImage.release()
                // 根据需要休眠一段时间;比如上层播放视频时每帧之间是有间隔的
                NSThread.sleepForTimeInterval(0.001)
            }
        })
    }
    // 完成回调
    guard self.delegate != nil else {
        print("代理没设置")
        return
    }
    self.delegate?.movieDecoderFinishCallBack(self)
}
  1. CMSampleBuffer--> CGImageRef的方法我是在Objective-C里面处理的,因为swift的autoreleasepool里面不能return
-(CGImageRef)cgImageFromSampleBufferRef:(CMSampleBufferRef)sampleBufferRef {
     @autoreleasepool {
        // 为媒体数据设置一个CMSampleBufferRef
        CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBufferRef);
        // 锁定 pixel buffer 的基地址,保证在内存中可用
        CVPixelBufferLockBaseAddress(imageBuffer, 0);
        // 得到 pixel buffer 的基地址
        void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
        // 得到 pixel buffer 的行字节数
        size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
        // 得到 pixel buffer 的宽和高
        size_t width = CVPixelBufferGetWidth(imageBuffer);
        size_t height = CVPixelBufferGetHeight(imageBuffer);
        // 创建一个依赖于设备的 RGB 颜色空间
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        // 用抽样缓存的数据创建一个位图格式的图形上下文(graphic context)对象
        CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
        //根据这个位图 context 中的像素创建一个 Quartz image 对象
        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下一个CMSampleBuffer。每一个CMSampleBuffer在缓冲区有一个单独的样本帧(视频样本帧)相关联。

  2. CVImageBufferRef
    有了CMSampleBuffer,我们就可以在图像缓冲区创建CVImageBufferRef(Pixel buffers 像素缓冲区属于CVImageBufferRef派生类),有了这个图像缓冲区,我们就可以对它做像素级别的操作。

  3. CGBitmapContextCreate创建位图对象

  • data: 指向要渲染的绘制内存的地址。这个内存块的大小至少是(bytesPerRow*height)个字节
  • width: bitmap的宽度,单位为像素
  • height: bitmap的高度,单位为像素
  • bitsPerComponent: 内存中像素的每个组件的位数.例如,对于32位像素格式和RGB 颜色空间,你应该将这个值设为8.
  • bytesPerRow: bitmap的每一行在内存所占的比特数
  • colorspace: bitmap上下文使用的颜色空间。
  • bitmapInfo: 指定bitmap是否包含alpha通道,像素中alpha通道的相对位置,像素组件是整形还是浮点型等信息的字符串。

第六步和第七步都使用了autoreleasepool为了及时释放掉内存,网上看到很多网友说用这个方法都内存爆掉了,我加了这两个之后虽然还是在Leaks看到有内存泄露,但是没有出现过内存爆掉的情况

    @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回调执行self.images.removeAll()之外,我还在prepareForReuse方法里面将animation置成nil,然后发现无论怎么滚动,内存都稳定在50M以下,比以前动辄几百M的好多了,不会出现crash的情况,不过依然会出现内存泄露的问题。


最后在网上找到一个感觉像是处理内存泄露的方法,具体我没考究,不过应该可以提供些许思路:
把一个视频拆分成多个AVAssetTrack,这样做的原因是因为,使用AVAssetReader读取每一帧SampleBuffer的数据是需要把数据加载到内存里面去的,如果直接把整个视频的SampleBuffer加载到内存,会造成闪退
链接:GitHub:KayWong/VideoReverse 使用AVFoundation实现视频倒序

以上代码参考链接:
IOS 微信聊天发送小视频的秘密(AVAssetReader+AVAssetReaderTrackOutput播放视频)

国庆期间我会写一个demo出来,现在暂时没时间。

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

推荐阅读更多精彩内容

  • --绘图与滤镜全面解析 概述 在iOS中可以很容易的开发出绚丽的界面效果,一方面得益于成功系统的设计,另一方面得益...
    韩七夏阅读 2,718评论 2 10
  • 本篇文章是基于谷歌有关Graphic的一篇概览文章的翻译:http://source.android.com/de...
    lee_3do阅读 7,113评论 2 21
  • 转载请带上出处, 谢谢. 一个 Graphics Context 代表一个绘制目标, 它包含绘制系统用于完成绘制指...
    Falme丶阅读 1,788评论 0 2
  • 难得在夜深人静之时心也是静的,无执于早起,便莫名的在台灯前思考起这个问题来。 “人是什么”是哲学、宗教、科学都关注...
    一只学术小狐狸阅读 1,132评论 0 3
  • 真的有好久好久不写东西了啊。最近比较懒惰。有趣的事情发生了不少,但就是懒得记下来,明明并不是没有时间~ ...
    画楼西阅读 174评论 0 1