iOSAVFoundation 拍片专用

iOS MP4转CGImageRef数组循环播放(AVAsset

2016-09-27  本文已影响558人  月咏蝴蝶

开始实现之前,先介绍一下 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创建位图对象


第六步和第七步都使用了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出来,现在暂时没时间。

上一篇下一篇

猜你喜欢

热点阅读