V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zoule
V2EX  ›  视频技术

AVAssetReader 视频数据读取

  •  
  •   zoule · 2021-01-18 15:40:50 +08:00 · 849 次点击
    这是一个创建于 1187 天前的主题,其中的信息可能已经有所发展或是发生改变。

    AVAssetReader 介绍

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

    AVAssetReader 初始化

    使用 AVAsset 对 AVAssetReader 进行初始化,前面也说了初始化之后就会加载样本数据,所以这一步就已经会对内存产生印象,如果内存紧张就不要预先初始化。

     NSError *createReaderError;
     _reader = [[AVAssetReader alloc]initWithAsset:_asset error:&createReaderError];
    

     

    AVAssetReader 设置 Output

    在开始读取之前,需要添加 output 来控制读取初始化使用的 asset 中哪些 track,以及配置如何读取。 AVAssetReaderOutput 还有其他的子类实现,如 AVAssetReaderVideoCompositionOutput,AVAssetReaderAudioMixOutput 和 AVAssetReaderSampleReferenceOutput 。 这里使用 AVAssetReaderTrackOutput 演示。需要一个 track 来初始化,track 从 asset 中获取。

    NSArray tracks = [_asset tracksWithMediaType:AVMediaTypeAudio];
    if (tracks.count > 0) {
        AVAssetTrack audioTrack = [tracks objectAtIndex:0];
    }
    

    或者

     NSArray tracks = [_asset tracksWithMediaType:AVMediaTypeVideo];
    if (tracks.count > 0) {
        AVAssetTrack videoTrack = [tracks objectAtIndex:0];
    }
    

    还可以对输出的格式进行配置,更多配置可以查阅文档。

    NSDictionary * const VideoAssetTrackReaderOutputOptions = @{(id) kCVPixelBufferOpenGLESCompatibilityKey : @(YES),
                                                                (id) kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary],
                                                                (NSString*)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)};
    _readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:_track
                                                               outputSettings:VideoAssetTrackReaderOutputOptions];
    if ([_reader canAddOutput:_readerOutput]) {
        [_reader addOutput:_readerOutput];
    }
    

     

    seek 操作

    AVAssetReader 并不适合频繁随机读取的操作,如果需要频繁 seek 可能需要别的方式实现。 在开始读取之前,可以对读取的范围进行设置,当开始读取后不可以修改,只能顺序向后读。 有两种方案来调整读取范围:

    1. output 中可以设置 supportsRandomAccess,当为 true 时,可以重置读取范围,但需要调用方调用 copyNextSampleBuffer,直到该方法返回 NULL 。
    2. 或者重新初始化一个 AVAssetReader 来设置读取时间。 如果尝试第一种方案,需要使用 seek,可以尝试每次设置一个不太长的区间,以保证读取完整个区间不会耗时太多,且时间间隔最好以关键帧划分。

     

    读取数据

    _reader.timeRange = range;
    [_reader startReading]; 
    _sampleBuffer = [_readerOutput copyNextSampleBuffer];
    

    CMSampleBuffer 中提供了方法获取解码数据,比如获取图像信息可以使用

    CVImageBufferRef pixelBuffer = 
    
    CMSampleBufferGetImageBuffer(_sampleBuffer);
    

    需要注意,当 CMSampleBuffer 使用完毕,需要调用 release 来释放

    CFRelease(_sampleBuffer);
    

     

    代码示例

    NSDictionary * const AssetOptions = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
    NSDictionary * const VideoAssetTrackReaderOutputOptions = @{(id) kCVPixelBufferOpenGLESCompatibilityKey : @(YES),
                                                                    (id) kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary],
                                                                    (NSString*)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)};
    _videoAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath: filePath]
                                                                        options:AssetOptions];
    _videoTrack = [[mPrivate->mVideoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
    if (_videoTrack) {
        NSError *createReaderError;
        _reader = [[AVAssetReader alloc] initWithAsset:_videoAsset error:&createReaderError];
        if (!createReaderError) {
            _readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:mPrivate->_videoTrack
                                                                                 outputSettings:VideoAssetTrackReaderOutputOptions];
            _readerOutput.supportsRandomAccess = YES;
            if ([_reader canAddOutput:_readerOutput])
            {
                [_reader addOutput:_readerOutput];
            }
            [_reader startReading];
    
            if (_reader.status == AVAssetReaderStatusReading || _reader.status == AVAssetReaderStatusCompleted) {
                CMSampleBufferRef samplebuffer = [_readerOutput copyNextSampleBuffer];
                if (samplebuffer) {
                //绘制 samepleBuffer 中的画面
                    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(samplebuffer);
                    CVPixelBufferLockBaseAddress(imageBuffer, 0);
                    uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer);
                    size_t width = CVPixelBufferGetWidth(imageBuffer);
                    size_t height = CVPixelBufferGetHeight(imageBuffer);
                    size_t bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
                    size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer);
    
                    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
                    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, baseAddress, bufferSize, NULL);
                    CGImageRef cgImage = CGImageCreate(width, height, 8, 32, bytesPerRow, rgbColorSpace, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrderDefault, provider, NULL, true, kCGRenderingIntentDefault);
                    CGImageRelease(cgImage);
                    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
                    CFRelease(samplebuffer);
                }
            }
        }
    
    }
    
    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   951 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 20:24 · PVG 04:24 · LAX 13:24 · JFK 16:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.