AVPlayer相关笔记

作者: 字节码 | 来源:发表于2016-11-12 00:11 被阅读284次
    • 1.NSAssert

    NSAssert()是一个宏,用于开发阶段调试程序中的Bug,通过为NSAssert()传递条件表达式来断定是否属于Bug,满足条件返回真值,程序继续运行,如果返回假值,则抛出异常,并且可以自定义异常描述。
    NSAssert()的源码定义

    #define NSAssert(condition, desc, ...)  \
        do {                \
        __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
        if (!(condition)) {     \
                NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
                __assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
            [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
            object:self file:__assert_file__ \
                lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
        }               \
            __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
        } while(0)
    #endif
    
    • 第一个参数condition是条件表达式,值为YES或NO;第二个参数desc为异常描述,通常为NSString。当conditon为YES时程序继续运行,为NO时,则抛出带有desc描述的异常信息。NSAssert()可以出现在程序的任何一个位置。

    • NSAssert和assert 区别
      NSAssert和assert都是断言,主要的差别是assert在断言失败的时候只是简单的终止程序,而NSAssert会报告出错误信息并且打印出来.所以只使用NSAssert就好,可以不去使用assert。

    • 2.NS_ASSUME_NONNULL_BEGIN & NS_ASSUME_NONNULL_END宏

    在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable的指针。
    __nullable 和 __nonnull 。从字面上我们可以猜到,__nullable表示对象可以是NULL或nil,而__nonnull表示对象不应该为空。当我们不遵循这一规则时,编译器就会给出警告。

    • 注意:
      • typedef,定义的类型的nullability,特性通常依赖于上下文,即使是在Audited Regions中,也不能假定它为nonnull。
      • 复杂的指针类型(如id*)必须显示去指定是non null还是nullable。例如,指定一个指向nullable对象的nonnulla指针,可以使用”__nullable id * __nonnull”。
        我们经常使用的NSError **通常是被假定为一个指向nullable NSError对象的nullable指针。
    • 3.合理使用NS_DESIGNATED_INITIALIZER和NS_UNAVAILABLE这两个宏

    • Objective-C 中主要通过NS_DESIGNATED_INITIALIZER宏来实现指定构造器的。这里之所以要用这个宏,往往是想告诉调用者要用这个方法去初始化(构造)类对象。

    • 通过NS_UNAVAILABLE,可以在编译期禁用父类的方法,算是不完美中的完全吧,我们可以禁用掉一些不合理的类成员,来达到一个比较好的封装效果。

    • 怎样避免使用NS_DESIGNATED_INITIALIZER产生的警告

    如果子类指定了新的初始化器,那么在这个初始化器内部必须调用父类的Designated Initializer。并且需要重写父类的Designated Initializer,将其指向子类新的初始化器。
    更好的做法
    如果指定一个新的方法为初始化器NS_DESIGNATED_INITIALIZER,大多是不想让调用者调用父类的初始化函数,只希望通过该类指定的初始化进行初始化,这时候就可以用NS_UNAVAILABLE宏指定系统的初始化器为不可用即可,那么外界在要给我这个类对象进行初始化时使用指定的方法即可。

    比如下面代码:

    #import <UIKit/UIKit.h>
    
    @interface XYView : UIView
    
    NS_ASSUME_NONNULL_BEGIN
    
    // .h中让系统默认的初始化器不可用(NS_UNAVAILABLE)
    - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
    - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
    // 指定这个方法为初始化构造器,外界调用这个方法初始化对象即可
    - (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color right:(BOOL)flag NS_DESIGNATED_INITIALIZER;
    
    NS_ASSUME_NONNULL_END
    @end
    
    // .m实现
    - (instancetype)initWithFrame:(CGRect)frame {
        NSAssert(NO, nil);
        @throw nil;
    }
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        NSAssert(NO, nil);
        @throw nil;
    }
    // .m实现指定的初始化器方法中别忘记让当前类调用父类的初始化器方法
    - (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color right:(BOOL)flag {
        if (self = [super initWithFrame:frame]) {    
        }
        return self;
    }
    

    此时外界给我这个对象进行初始化时,系统就不会提示那两个声明NS_UNAVAILABLE这个宏的初始化方法了,可以使用我指定的方法进行初始化了,当然你也可以把init方法给设为不可用,如下图

    Snip20161111_1.png

    4.iOS 摄像头使用-UIImagePickerController

    1.Source type: 这个参数是用来确定是调用摄像头还是调用图片库.如果是 UIImagePickerControllerSourceTypeCamera 就是调用摄像头,如果是UIImagePickerControllerSourceTypePhotoLibrary 就是调用图片库,如果是UIImagePickerControllerSourceTypeSavedPhotosAlbum 则调用iOS设备中的胶卷相机的图片.

    2.Media types:在拍照时,用来指定是拍静态的图片还是录像.kUTTypeImage 表示静态图片, kUTTypeMovie表示录像.
    注意:KUTTYpeImage和kUTTypeMovie常量是MobileCoreServices框架中的CFStringRef类型常量,需要导入#import <MobileCoreServices/MobileCoreServices.h>头文件,要转换下格式为NSString

    3.Editing controls :用来指定是否可编辑.将allowsEditing 属性设置为YES表示可编辑,NO表示不可编辑,设置当拍照完或在相册选完照片后,是否跳到编辑模式进行图片剪裁。只有当showsCameraControls属性为YES时才有效果 imagepicker.allowsEditing = YES;

    4.代理方法:- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info;成功获取视频或相册后的回调
    如以下代码

      UIImagePickerController *myImagePickerController = [[UIImagePickerController alloc] init];
      myImagePickerController.sourceType =  UIImagePickerControllerSourceTypePhotoLibrary;
      // 让摄像头只能摄影而不能拍照,设置mediaTypes为kUTTypeMovie
      myImagePickerController.mediaTypes =
      [[NSArray alloc] initWithObjects: (NSString *) kUTTypeMovie, nil];
      myImagePickerController.delegate = self;
      myImagePickerController.editing = NO;
      [self presentViewController:myImagePickerController animated:YES completion:nil];
    
    
    • 4. AVAsset

    AVFoundation给我们提供了一个非常大的可扩展的框架,我们可以通过这个框架,对媒体资源进行捕捉,组合,播放,处理等功能, AVAsset则是AVFoundation框架中,一个非常重要的类

    • 1.何为AVAsset

    AVAsset是一个抽象类和不可变类,定义了媒体资源混合呈现的方式.可以让我们开发者在处理时基媒体提供了一种简单统一的方式,它并不是媒体资源,但是它可以作为时基媒体的容器.

    • 2.创建方法

    当我们想为一个媒体资源创建AVAsset对象时,可以通过URL对它进行初始化,URL可以是本地资源也可以是一个网络资源

    AVAsset *asset = [AVAsset asetWithURL:assetUrl];```
    当通过asetWithURL方法进行创建时,实际上是创建了AVAsset子类AVUrlAsset的一个实例,而AVAsset是一个抽象类,不能直接被实例化.
    通过AVUrlAsset我们可以创建一个带选项(optional)的asset,以提供更精确的时长和计时信息
    
    - ###5.AVPlayer播放视频
    >在iOS开发中,播放视频通常有两种方式:一种是使用MPMoviePlayerController(需要导入MediaPlayer.Framework),还有一种是使用AVPlayer。
    这两个类的区别简而言之就是MPMoviePlayerController使用更简单,功能不如AVPlayer强大,而AVPlayer使用稍微麻烦点,不过功能更加强大。
    单纯使用AVPlayer类是无法显示视频的,要将视频层添加至AVPlayerLayer中,这样才能将视频显示出来。
    
       - 播放Assets
         1.一个播放器就是控制asset播放的对象,比如开始和结束,seek到指定的时间。可以使用AVPlayer来播放单个asset,用AVQueuePlayer来播放多个连续的asset。
      2.一个player向你提供播放的信息,如果需要,你通过player的状态同步显示到界面上。你也可以直接把player的输出显示到指定的动画层(AVPlayerLayer或者AVSynchronizedLayer)```多个layer的情况:你可以创建多个AVPlayerLayer对象,但是只有最近创建的layer才会显示视频画面。```
      3.虽然是播放asset,但是不能直接把asset传给AVPlayer对象,你应该提供AVPlayerItem对象给AVPlayer。一个player item管理着和它相关的asset。一个player item包括player item tracks-(AVPlayerItemTrack对象,表示asset中的tracks)
    
       - 用AVPlayerLayer播放一个video文件,步骤如下:
    1、用AVPlayerLayer配置一个view 
    2、创建一个AVPlayer 
    3、用video文件创建一个AVPlayerItem对象,并且用kvo观察他的status 
    4、当收到item的状态变成可播放的时候,播放按钮启用
    5、播放,结束之后把播放头设置到起始位置
    
    
    - ###6. CALayer - contentsGravity属性
    >当我们使用Cocoa的视图的时候,我们必须继承NSView或者UIView并且重载函数drawRect:来显示任何内容。但是CALayer实例可以直接使用,而无需继承子类。因为CALayer是一个键-值编码兼容的容器类,你可以在实例里面存储任意值,所以子类实例化完全可以避免
    
      - 给CALayer提供内容
    >你可以通过以下任何一种方法指定CALayer实例的内容:
    1.使用包含图片内容的CGImageRef来显式的设置图层的contents的属性。
    2.指定一个委托,它提供或者重绘内容。
    3.继承CALayer类重载显示的函数。
    
        - 1设置contents属性
    图层的图片内容可以通过指定contents属性的值为CGImageRef。当图层被创建的时候或者在任何其他时候,这个操作可以在其他实体上面完成,比如
    
    CALayer *layer = [CALayer layer];
      layer.position = CGPointMake(50.0, 50.0);
      layer.bounds = CGRectMake(0, 0, 200, 200);
      layer.contents = (__bridge id _Nullable)([[UIImage imageNamed:@"image"] CGImage]);```
    
      1. 修改图层内容的位置
        CALayer的属性contentsGravity允许你在图层的边界内容修改图层的contents图片的位置或者伸缩值。默认情况下,内容的图像完全填充层的边界,忽视自然的图像宽高比。
        使用contentsGravity位置常量,你可以指定图片位于图层任何一个边界,比如位于图层的角落,或者图层边界的中心。然而当你使用位置常量的时候,contentsCenter属性会被忽略。
        “图层的坐标系”标识了所支持的内容位置和他们相应的常量。


        layer_contentsgravity1.jpg

        通过设置contentsGravity属性为其他一个常量。图层的内容图片可以被向上或者向下拉伸, 仅当使用其他任何一个调整大小的常量的时候,contentsCenter属性才会对内容图片起作用。

    • 7.AVPlayer视频播放相关

    1.Video Gravity 视频播放时的拉伸方式
    视频播放时的拉伸样式.jpg

    Apple定义的视频播放时拉伸方式的三种常量

    AVF_EXPORT NSString *const AVLayerVideoGravityResizeAspect NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVLayerVideoGravityResizeAspectFill NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVLayerVideoGravityResize NS_AVAILABLE(10_7, 4_0);
    
    AVLayerVideoGravityResize,           // 非均匀模式。两个维度完全填充至整个视图区域
    AVLayerVideoGravityResizeAspect,      // 等比例填充,直到一个维度到达区域边界
    AVLayerVideoGravityResizeAspectFill,  // 等比例填充,直到填充满整个视图区域,其中一个维度的部分区域会被裁剪
    

    比如以下设置playerLayer

    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
    playerLayer.contentsGravity = AVLayerVideoGravityResizeAspect;
    

    2.actionAtItemEnd

    @property (nonatomic) AVPlayerActionAtItemEnd actionAtItemEnd;
    

    这是一个AVPlayerActionAtItemEnd的枚举类型,表示当项目的播放到达其结束时间时播放器应当执行的动作
    定义如下
    typedef NS_ENUM(NSInteger, AVPlayerActionAtItemEnd)
    {
    AVPlayerActionAtItemEndAdvance = 0, // 结束前进
    AVPlayerActionAtItemEndPause = 1, //结束暂停
    AVPlayerActionAtItemEndNone = 2,// 不结束
    };

    3.控制快进,后退的方法

    - (void)seekToTime:(CMTime)time
    toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter;
    

    使用此方法可以寻找当前播放器项目的指定时间。求得的时间将在[时间容差前,时间+容差后]范围内,并且可能与指定的时间有所不同,以提高效率。对于toleranceBefore和toleranceAfter,请求kCMTimeZero以请求样本精确寻找,这可能招致额外的解码延迟。使用beforeTolerance消息传递此方法:kCMTimePositiveInfinity和afterTolerance:kCMTimePositiveInfinity与消息seekToTime:直接相同
    比如:[self.player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];

    • 8.CMTimeMake 和 CMTimeMakeWithSeconds

    CMTime是专门用于标识电影时间的结构体,通常用如下两个函数来创建CMTime

    Apple定义CMTime的源码:
     typedef struct
        {
            CMTimeValue value; // CMTime的值: value / timescale = seconds
            CMTimeScale timescale; // 每秒的帧数
            CMTimeFlags flags; /*! @field flags The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. */
            CMTimeEpoch epoch; /*时间区分实际上不同的相等时间戳,因为循环,多项目排序等。将在比较期间使用:更大的时期发生在较小的时期之后。加法/减法只能在单个时期内进行,然而,因为时代长度可能是未知/可变的*/
        } CMTime;
    

    第一方式创建CTTime

    CMTime CMTimeMake (
       int64_t value,    //表示 当前视频播放到的第几桢数
       int32_t timescale //每秒的帧数
    );```
    注意:```CMTimeMake(time, timeScale)```,第一个参数time指的是时间(不是秒),而时间time要转换为秒就要看第二个参数timeScale了,timeScale指的是1秒需要由几个frame构造(可以视为fps每秒的帧数),因为真正要表达的时间会是time/timeScale才是秒
    
    >经常看到别的开源项目中这样定义CMTime
    ```CMTime firstframe = CMTimeMake(1,10);
     CMTime lastframe = CMTimeMake(10, 10);```
    上面的代码可以这么理解,视频的fps(帧率)是10,firstframe是第一帧的视频时间为0.1秒,lastframe是第10帧视频时间为1秒。
    或者换种写法   CMTime curFrame = CMTimeMake(第几帧, 帧率)
    
    
    >第二种方式创建CMTime
    

    CMTime CMTimeMakeWithSeconds(
    Float64 seconds, //第几秒的截图,是当前视频播放到的帧数的具体时间
    int32_t preferredTimeScale //首选的时间尺度 "每秒的帧数"
    );

    
    - ###9.AVAsset、AVMutableComposition视频裁剪示例
    >与之相关的这些类有些抽象,用代码将视频合成其实与绘声绘影/vegas/Final Cut Pro等软件将视频合成的过程类似,首先了解下这类软件一些相关知识:一个工程文件中有很多轨道,如音频轨道1,音频轨道2,音频轨道3,视频轨道1,视频轨道2等等,每个轨道里有许多素材,对于每个视频素材,它可以进行缩放、旋转等操作,素材库中的视频拖到轨道中会分为视频轨和音频轨两个轨道。
      - 这里用这些软件里的一些术语类来比这些类: 
    

    AVAsset:素材库里的素材;
    AVAssetTrack:素材的轨道;
    AVMutableComposition :一个用来合成视频的工程文件;
    AVMutableCompositionTrack :工程文件中的轨道,有音频轨、视频轨等,里面可以插入各种对应的素材;
    AVMutableVideoCompositionLayerInstruction:视频轨道中的一个视频,可以缩放、旋转等;
    AVMutableVideoCompositionInstruction:一个视频轨道,包含了这个轨道上的所有视频素材;
    AVMutableVideoComposition:管理所有视频轨道,可以决定最终视频的尺寸,裁剪需要在这里进行;
    AVAssetExportSession:配置渲染参数并渲染。```

    接下来就用这种类比的方式裁剪一个视频:

    1.将素材拖入到素材库中
    AVAsset *asset = [AVAsset assetWithURL:outputFileURL];
    AVAssetTrack *videoAssetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0];//素材的视频轨
    AVAssetTrack *audioAssertTrack = [[asset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0];//素材的音频轨
    
    2.将素材的视频插入视频轨,音频插入音频轨
    AVMutableComposition *composition = [AVMutableComposition composition];//这是工程文件
    AVMutableCompositionTrack *videoCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];//视频轨道
    [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];//在视频轨道插入一个时间段的视频
    AVMutableCompositionTrack *audioCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];//音频轨道
    [audioCompositionTrack insertTimeRange: CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration) ofTrack:audioAssertTrack atTime:kCMTimeZero error:nil];//插入音频数据,否则没有声音  
      
    3.裁剪视频,就是要将所有视频轨进行裁剪,就需要得到所有的视频轨,而得到一个视频轨就需要得到它上面所有的视频素材
    AVMutableVideoCompositionLayerInstruction *videoCompositionLayerIns = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoAssetTrack];
    [videoCompositionLayerIns setTransform:videoAssetTrack.preferredTransform atTime:kCMTimeZero];//得到视频素材(这个例子中只有一个视频)
    AVMutableVideoCompositionInstruction *videoCompositionIns = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    [videoCompositionIns setTimeRange:CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration)];//得到视频轨道(这个例子中只有一个轨道)
    AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
    videoComposition.instructions = @[videoCompositionIns];
    videoComposition.renderSize = CGSizeMake(...);//裁剪出对应的大小
    videoComposition.frameDuration = CMTimeMake(1, 30);
    
    4.导出
    AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality];
    exporter.videoComposition = videoComposition;
    // 视频输出的地址
    exporter.outputURL = [NSURL fileURLWithPath:_outputFilePath isDirectory:YES];
    // 输出文件的类型
    exporter.outputFileType = AVFileTypeMPEG4;
    exporter.shouldOptimizeForNetworkUse = YES;
    // 剪辑视频:注意它是一个异步操作,outputFileType属性的设置
    [exporter exportAsynchronouslyWithCompletionHandler:^{    
         if (exporter.error) {   
            //...     
            }else{ 
            //...          
            }    
    }];```
    
    - ###10.视频导出选项
    

    AVF_EXPORT NSString *const AVAssetExportPresetLowQuality NS_AVAILABLE(10_11, 4_0);
    AVF_EXPORT NSString *const AVAssetExportPresetMediumQuality NS_AVAILABLE(10_11, 4_0);
    AVF_EXPORT NSString *const AVAssetExportPresetHighestQuality NS_AVAILABLE(10_11, 4_0);```

    这些导出选项可用于生成具有适合于设备的视频大小的电影文件。导出不会将视频从较小的大小缩放。视频将使用压缩H.264和音频将使用AAC进行压缩。

    + (NSArray<NSString *> *)exportPresetsCompatibleWithAsset:(AVAsset *)asset;
    

    AVAssetExportSession导出视频的状态枚举类型AVAssetExportSessionStatus

    typedef NS_ENUM(NSInteger, AVAssetExportSessionStatus) {
                 AVAssetExportSessionStatusUnknown,   // 导出状态未知
                 AVAssetExportSessionStatusWaiting,   // 导出等待状态
                 AVAssetExportSessionStatusExporting, // 正在导出状态
                 AVAssetExportSessionStatusCompleted, // 已导出完成状态
                 AVAssetExportSessionStatusFailed,    // 导出失败状态
                 AVAssetExportSessionStatusCancelled  // 已取消导出状态
    };
    
    

    注意: 导出视频的方法是异步执行的,在导出完成后回到主线程保存视频到相册中,如下

    // 导出视频
    [self.exportSession exportAsynchronouslyWithCompletionHandler:^{
                switch ([self.exportSession status]) {
                    case AVAssetExportSessionStatusFailed
                        NSLog(@"Export failed: %@", [[self.exportSession error] localizedDescription]);
                        break;
                    case AVAssetExportSessionStatusCancelled:
                        
                        NSLog(@"Export canceled");
                        break;
                    default:
                        NSLog(@"NONE");
                        dispatch_async(dispatch_get_main_queue(), ^{
                            
                            NSURL *movieUrl = [NSURL fileURLWithPath:self.tempVideoPath];
                            UISaveVideoAtPathToSavedPhotosAlbum([movieUrl relativePath], self,@selector(video:didFinishSavingWithError:contextInfo:), nil);
                        });
                        
                        break;
                }
            }];
    
    
    • 11. AVAssetImageGenerator

    AVFoundation生成视频缩略图主要靠如下两个类

    • 1.AVURLAsset
      该类是AVAsset的子类,AVAsset类专门用于获取多媒体的相关信息,包括获取多媒体的画面、声音等信息。而AVURLAsset子类的作用则是根据NSURL来初始化AVAsset对象。
    • 2.AVAssetImageGenerator
      该类专门用于截取视频指定帧的画面。

    使用AVFoundation生成视频缩略图的步骤如下:

    • 1.根据视频的NSURL创建AVURLAsset对象
    • 2.根据AVURLAsset对象创建AVAssetImageGenerator对象
    • 3.调用AVAssetImageGenerator对象的copyCGImageAtTime:actualTime:error:方法来获取该视频指定时间点的视频截图。
      该方法的第一个CMTime参数用于指定获取哪个时间点的视频截图,第2个CMTime参数用于获取实际截图 位于哪个时间点.
      其中CMTime是专门用于标识电影时间的结构体,通常用如下两个函数来创建CMTime.
      CMTimeMake(int64_t value, int_32 timescale):第1个参数代表获取第几帧的截图,第2个参数代表每秒的帧数.因此实际截取的时间点是value/timescale.
      CMTimeMakeWithSeconds(Float64 seconds, int32_t preferredTimeScale):第1个参数代表获取第几秒的截图,第2个参数则代表每秒的帧数.

    相关文章

      网友评论

        本文标题:AVPlayer相关笔记

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