AVAssetReader视频数据读取


AVAssetReader介绍

可以通过AVAssetReader获取视频文件里媒体样本,可以直接从存储器中读取未解码的原始媒体样本,获得解码成可渲染形式的样本。
文档里说明AVAssetrader管道内部是多线程的。初始化之后,读取器在使用前加载并处理合理数量的样本数据,以copyNextSampleBuffer(AVAssetReaderOutput)等检索操作的延迟非常低。但AVAssetReader还是不适用于实时源,并且它的性能也不能保证用于实时操作。
由于使用前需要加载并处理一些样本数据,导致占用的内存可能会比较大,需要注意同一时间使用的reader个数不要过多,视频像素越高,占用的内存也会越大。

一、AVAsset的获取

这里通过NSURL的方式来创建对应的AVAsset

- (void)customInit {
    NSDictionary* options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:(id)AVURLAssetPreferPreciseDurationAndTimingKey];
    
    // 将url对应的内容载入到url资产中
    AVURLAsset* inputAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:options];
    
    __weak typeof(self) weakSelf = self;
    // 由于url对应的video可能比较大会比较慢,所以需要异步等待载入完毕
    [inputAsset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:@"tracker"] completionHandler:^{
        // 异步载入完毕之后进行判断
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSError* error = nil;
            AVKeyValueStatus valueStatus = [inputAsset statusOfValueForKey:@"tracker" error:&error];
            if (valueStatus != AVKeyValueStatusLoaded) {
                NSLog(@"load failed %@", error);
                return;
            }
            
            [weakSelf processWithAsset:inputAsset];
        });
    }];
}

因为AVURLAsset是继承自AVAsset,所以这里创建AVURLAsset是可以的

这里参数设置使用AVURLAssetPreferPreciseDurationAndTimingKey,该参数表示 asset 应该提供精确的时长并能根据时间准确地随机访问,提供这样的能力是需要开销更大的计算的。当只是想播放视频时可以不设置这个选项。这里需要每一帧的渲染,则需要精确的时长。

二、AVAssetReader的创建

- (void)processWithAsset:(AVAsset *)asset {
    [lock lock];
    NSLog(@"processWithAsset");
    NSError* error = nil;
    // 通过url资产创建资产reader
    assetReader = [AVAssetReader assetReaderWithAsset:asset error:&error];
    
    // 由于是要每一帧的进行读取并且是全部读取,则需要设置为kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
    NSDictionary* outputSettings = [NSDictionary dictionaryWithObject:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) forKey:(id)kCVPixelBufferPixelFormatTypeKey];
    // 创建资产reader追踪
    // [asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] 返回对应的track
    readerVideoTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] outputSettings:outputSettings];
    
    // 将追踪器加入到资产reader
    [assetReader addOutput:readerVideoTrackOutput];
    
    // 开始读
    if (![assetReader startReading]) {
        NSLog(@"error startreading %@", asset);
    }
    
    [lock unlock];
}

如上细节已经在注释中说明,有个注意的点是追踪器需要在startReading之前设置。

三、每一帧采样缓冲区的读取

- (CMSampleBufferRef)readBuffer {
    [lock lock];
    
    CMSampleBufferRef sampleBufferRef = nil;
    if (readerVideoTrackOutput) {
        sampleBufferRef = [readerVideoTrackOutput copyNextSampleBuffer];
    }
    
    if (assetReader && assetReader.status == AVAssetReaderStatusCompleted) {
        NSLog(@" re customInit");
        assetReader = nil;
        readerVideoTrackOutput = nil;
        
        [self customInit];
    }
    
    [lock unlock];
    
    return sampleBufferRef;
}

如上[readerVideoTrackOutput copyNextSampleBuffer] 是按顺序读取了每一帧的buffer

当本次视频读取结束之后,则重新再一次创建asset

参考:

https://www.jianshu.com/p/7114536d705a

https://www.cnblogs.com/samirchen/p/7073992.html