ios

《AV Foundation 开发秘籍》读书笔记(四)

2018-01-02  本文已影响31人  mayan29

第四章 视频播放

1. 主要框架

AVPlayer

AV Foundation 的播放都围绕 AVPlayer 类展开,AVPlayer 是一个用来播放基于时间的视听媒体的控制器对象,支持播放从本地、分布下载或通过 HTTP Live Streaming 协议得到的流媒体。

AVPlayer 是一个不可见组件,如果播放音频文件,没有可视化的用户界面也不会有什么问题,如果播放视频文件,要将视频资源导出到用户界面,需要使用 AVPlayerLayer 类。

AVPlayer 只管理一个单独资源的播放,不过框架还提供了 AVPlayer 的一个子类 AVQueuePlayer,可以用来管理一个资源队列。当你需要在一个序列中播放多个条目或者为音频、视频资源设置播放循环时可使用该子类。

AVPlayerLayer

AVPlayerLayer 构建于 Core Animation 之上,Core Animation 本身具有基于时间的属性,并且由于它基于 OpenGL,所以具有很好的性能,能非常好地满足 AV Foundation 的各种需要。

AVPlayerLayer 使用起来简单,开发者可以自定义的只有 videoGravity 属性,用来确定视频的拉伸或缩放程度。

AVPlayerItem

我们最终的目的是使用 AVPlayer 播放 AVAsset。但是 AVAsset 只包含媒体资源的静态信息,无法实现播放功能,所以,需要通过 AVPlayerItem 和 AVPlayerItemTrack 构建响应的动态内容。

AVPlayerItem 会建立媒体资源动态视角的数据模型,保存 AVPlayer 在播放时呈现的状态。AVPlayerItem 由一个或者多个媒体曲目组成,由 AVPlayerItemTrack 类建立模型。AVPlayerItemTrack 实例用于表示播放器条目中的类型统一的媒体流,比如音频或者视频。AVPlayerItem 中的曲目直接与基础 AVAsset 中的 AVAssetTrack 实例相对应。

2. 视频播放

- (void)viewDidLoad {
    [super viewDidLoad];
   
    // AVPlayerItem:提供数据
    NSURL *assetURL = [[NSBundle mainBundle] URLForResource:@"一骑当千01" withExtension:@"mp4"];
    AVAsset *asset = [AVAsset assetWithURL:assetURL];
    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
    [playerItem addObserver:self forKeyPath:@"status" options:0 context:nil];
    
    // AVPlayer:控制播放
    _player = [AVPlayer playerWithPlayerItem:playerItem];
    
    // AVPlayerLayer:显示播放
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
    playerLayer.frame = self.view.bounds;
    [self.view.layer addSublayer:playerLayer];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"status"]) {
        
        AVPlayerItem *playerItem = (AVPlayerItem *)object;
        if (playerItem.status == AVPlayerItemStatusReadyToPlay) {            
            [self.player play];
        }
    }
}

3. 时间处理

AVAudioPlayer 使用 NSTimeInterval 表示时间,但是 double 类型的运算会导致不精确的情况。此外,double 类型呈现时间信息无法做到自我描述,这就导致在使用不同时间轴进行比较和运算时比较困难。所以 AV Foundation 使用一种可靠性更高的方法来展示时间信息,这就是 CMTime

typedef struct {
    CMTimeValue value;      
    CMTimeScale timescale;  
    CMTimeFlags flags;      
    CMTimeEpoch epoch;  
} CMTime;

CMTime 是一种结构体,最关键的两个值为 value 和 timescale,value 是一个 64 位整数值,timescale 是一个 32 位整数值,分别为分子和分母。

// 0.5 s
CMTime halfSecond = CMTimeMake(1, 2);

// 5 s
CMTime fiveSeconds = CMTimeMake(5, 1);

// 44.1 kHz
CMTime oneSample = CMTimeMake(1, 44100);

// Zero time value
CMTime zeroTime = kCMTimeZero;

4. 显示字幕

显示字幕需要用到两个类:AVMediaSelectionGroup 和 AVMediaSelectionOption,AVMediaSelectionOption 表示 AVAsset 中的备用媒体呈现方式,比如音频、视频、文本轨道。这些轨道可能是指定语言的音频轨道、备用相机角度、指定语言字幕。

NSArray *mediaCharacteristics = self.asset.availableMediaCharacteristicsWithMediaSelectionOptions;

该数组中可能包含的字符串值为:

获取所有可选择字幕语言数组

- (NSArray *)getSubtitles {

    AVMediaSelectionGroup *group = [self.asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
    if (group) {
        NSMutableArray *subtitles = [NSMutableArray array];
        for (AVMediaSelectionOption *option in group.options) {
            [subtitles addObject:option.displayName];  // 语言字幕轨道名称,比如 English、Italian、Russian
        }
        return subtitles;
    } else {
        return nil;
    }
}

设置字幕

- (void)subtitleSelected:(NSString *)subtitle {
    
    AVMediaSelectionGroup *group =
        [self.asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
    
    for (AVMediaSelectionOption *option in group.options) {
        if ([option.displayName isEqualToString:subtitle]) {
            [self.playerItem selectMediaOption:option inMediaSelectionGroup:group];
            return;
        }
    }
    [self.playerItem selectMediaOption:nil inMediaSelectionGroup:group];
}

5. 缩略图

AVAssetImageGenerator 定义了两个方法实现从视频资源中检索图片:

  1. copyCGImageAtTime:actualTime:error: 允许在指定时间点捕捉图片,适合在展示视频缩略图。

  2. generateCGImagesAsynchronouslyForTimes:completionHandler:
    允许按照第一个参数所指定的时间段生成一个图片序列,该方法具有很高的性能,只需要调用这一个方法就可以生成一组图片

下面方法为第一种 copyCGImageAtTime:actualTime:error: 方法:

- (void)generateThumbnails {
    
    self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];
    
    // 默认情况下,捕捉的图片都保持原始维度。设置 maximumSize 的宽度,则会根据视频的宽高比自动设置高度值
    self.imageGenerator.maximumSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 0.0f);
    
    CMTime duration = self.asset.duration;
    
    // 将视频时间轴平均分为 20 个 CMTime 值
    NSMutableArray *times = [NSMutableArray array];
    CMTimeValue increment = duration.value / 20;
    CMTimeValue currentValue = 2.0 * duration.timescale;
    while (currentValue <= duration.value) {
        CMTime time = CMTimeMake(currentValue, duration.timescale);
        [times addObject:[NSValue valueWithCMTime:time]];
        currentValue += increment;
    }
    
    __block NSUInteger imageCount = times.count;
    __block NSMutableArray *images = [NSMutableArray array];
    
    AVAssetImageGeneratorCompletionHandler handler;
    
    // requestedTime : 请求的最初时间,它对应于生成图像的调用中指定的 times 数组中的值
    // imageRef      : 生成的 CGImageRef,如果在给定的时间点没有生成图片则为 NULL
    // actualTime    : 图片实际生成的时间,可能和请求时间不同。可以在生成图片前通过 AVAssetImageGenerator 实例设置 requestedTimeToleranceBefore 和 requestedTimeToleranceAfter 值来调整 requestedTime 和 actualTime 的接近程度
    // result        : AVAssetImageGeneratorResult 用来表示生成图片成功、失败、取消
    handler = ^(CMTime requestedTime,
                CGImageRef imageRef,
                CMTime actualTime,
                AVAssetImageGeneratorResult result,
                NSError *error) {
        
        if (result == AVAssetImageGeneratorSucceeded) {
            MYThumbnail *thumbnail =
                [MYThumbnail thumbnailWithImage:[UIImage imageWithCGImage:imageRef] time:actualTime];
            [images addObject:thumbnail];
        } else {
            NSAssert(error.localizedDescription, error.localizedDescription);
        }
        
        // 如果 imageCount 等于 0 这表明所有图片都处理完成
        if (--imageCount == 0) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.playerView.transport setStillsImages:images];
            });
        }
    };
    [self.imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:handler];
}

6. 封装框架

有很多知识点通过笔记是无法表达清楚的,具体实现还需看代码理解的透彻。根据 Learning-AV-Foundation Chapter 4 自行封装了一个视频播放、设置字幕、获取视频缩略图的框架,下载地址:https://github.com/Mayan29/MYAVFoundation

上一篇下一篇

猜你喜欢

热点阅读