美文网首页ios
《AV Foundation 开发秘籍》读书笔记(四)

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

作者: mayan29 | 来源:发表于2018-01-02 12:40 被阅读31次

    第四章 视频播放

    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;
    

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

    • AVMediaCharacteristicVisual(视频)
    • AVMediaCharacteristicAudible(音频)
    • AVMediaCharacteristicLegible(字幕或隐藏式字幕)

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

    - (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

    相关文章

      网友评论

        本文标题:《AV Foundation 开发秘籍》读书笔记(四)

        本文链接:https://www.haomeiwen.com/subject/gjmfnxtx.html