/VideoPlay

Primary LanguageObjective-C

我们在项目中有时会碰到视频相关的需求,一般的可以分为几种情况:

1. 简单的视频开发,对界面无要求,可直接使用系统UIImagePickerController。

(1)使用UIImagePickerController视频录制,短视频10秒钟

(2)在UIImagePickerController代理方法 didFinishPickingMediaWithInfo,使用AVAssetExportSession转码MP4(一般要兼容Android播放,iOS默认是mov格式)

(3)使用AFNetWorking上传到服务器

(4)网络请求,使用AVPlayerViewControlller在线播放视频流。(在iOS9之后,苹果已经弃用MPMoviePlayerController)

####1.1 UIImagePickerController

UIImagePickerController继承于UINavigationController。UIImagePickerController可以用来选择照片,它还可以用来拍照和录制视频。

  1. sourceType: 拾取源类型,sourceType是枚举类型:

    UIImagePickerControllerSourceTypePhotoLibrary:照片库,默认值 UIImagePickerControllerSourceTypeCamera:摄像头 UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿

  2. **mediaTypes:**媒体类型,默认情况下此数组包含kUTTypeImage,所以拍照时可以不用设置;但是当要录像的时候必须设置,可以设置为kUTTypeVideo(视频,但不带声音)或者kUTTypeMovie(视频并带有声音)

  3. **videoMaximumDuration:**视频最大录制时长,默认为10 s

  4. **videoQuality:**视频质量,枚举类型: UIImagePickerControllerQualityTypeHigh:高清质量 UIImagePickerControllerQualityTypeMedium:中等质量,适合WiFi传输 UIImagePickerControllerQualityTypeLow:低质量,适合蜂窝网传输 UIImagePickerControllerQualityType640x480:640480 UIImagePickerControllerQualityTypeIFrame1280x720:1280720 UIImagePickerControllerQualityTypeIFrame960x540:960*540

  5. **cameraDevice:**摄像头设备,cameraDevice是枚举类型: UIImagePickerControllerCameraDeviceRear:前置摄像头 UIImagePickerControllerCameraDeviceFront:后置摄像头

  6. **cameraFlashMode:**闪光灯模式,枚举类型:UIImagePickerControllerCameraFlashModeOff:关闭闪光灯UIImagePickerControllerCameraFlashModeAuto:闪光灯自动UIImagePickerControllerCameraFlashModeOn:打开闪光灯

  7. (BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)sourceType指定的源类型是否可用,sourceType是枚举类型: UIImagePickerControllerSourceTypePhotoLibrary:照片库 UIImagePickerControllerSourceTypeCamera:摄像头 UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿

  8. UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo)	//保存照片到相簿
  9. UISaveVideoAtPathToSavedPhotosAlbum(NSString *videoPath, id completionTarget, SEL completionSelector, void *contextInfo)	//保存视频到相簿

    示例:

        //使用UIImagePickerController视频录制
        UIImagePickerController *picker = [[UIImagePickerController alloc] init];
        picker.delegate = self;
        if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
            picker.sourceType = UIImagePickerControllerSourceTypeCamera;
        }
        
        //mediaTypes设置摄影还是拍照
        //kUTTypeImage 对应拍照
        //kUTTypeMovie  对应摄像
    //    NSString *requiredMediaType = ( NSString *)kUTTypeImage;
        NSString *requiredMediaType1 = ( NSString *)kUTTypeMovie;
        NSArray *arrMediaTypes=[NSArray arrayWithObjects:requiredMediaType1,nil];
        picker.mediaTypes = arrMediaTypes;
    //    picker.videoQuality = UIImagePickerControllerQualityTypeHigh;默认是中等
        picker.videoMaximumDuration = 60.; //60秒
        [self presentViewController:picker animated:YES completion:^{
            
        }];

1.2 AVAsset

AVAsset是一个抽象类和不可变类。定义了媒体资源混合呈现的方式。主要用于获取多媒体信息,既然是一个抽象类,那么当然不能直接使用。可以让开发者在处理流媒体时提供一种简单统一的方式,它并不是媒体资源,但是它可以作为时基媒体的容器。

实际上是创建的是它的子类AVUrlAsset的一个实例。通过AVUrlAsset我们可以创建一个带选项(optional)的asset,以提供更精确的时长和计时信息。比如通过AVURLAsset的duration计算视频时长。

/**
 获取视频时长

 @param url url
 @return s
 */
- (CGFloat)getVideoLength:(NSURL *)url{
    AVURLAsset *avUrl = [AVURLAsset assetWithURL:url];
    CMTime time = [avUrl duration];
    int second = ceil(time.value/time.timescale);
    return second;
}

1.3 AVAssetExportSession

AVAssetExportSession是用于为AVAsset源对象进行转码输出,可进行格式转码,压缩等。

 AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
    
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetMediumQuality];

    exportSession.outputURL = outputURL;
    exportSession.outputFileType = AVFileTypeMPEG4;
    exportSession.shouldOptimizeForNetworkUse= YES;
    [exportSession exportAsynchronouslyWithCompletionHandler:nil];

1.4 AVAssetImageGenerator

AVAssetImageGenerator能够取出视频中每一帧的缩略图或者预览图。举个🌰:

  // result
    UIImage *image = nil;
    
    // AVAssetImageGenerator
    AVAsset *asset = [AVAsset assetWithURL:videoURL];
    AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    imageGenerator.appliesPreferredTrackTransform = YES;
    
    // calculate the midpoint time of video
    Float64 duration = CMTimeGetSeconds([asset duration]);
    // 取某个帧的时间,参数一表示哪个时间(秒),参数二表示每秒多少帧
    // 通常来说,600是一个常用的公共参数,苹果有说明:
    // 24 frames per second (fps) for film, 30 fps for NTSC (used for TV in North America and
    // Japan), and 25 fps for PAL (used for TV in Europe).
    // Using a timescale of 600, you can exactly represent any number of frames in these systems
    CMTime midpoint = CMTimeMakeWithSeconds(duration / 2.0, 600);
    
    // get the image from
    NSError *error = nil;
    CMTime actualTime;
    // Returns a CFRetained CGImageRef for an asset at or near the specified time.
    // So we should mannully release it
    CGImageRef centerFrameImage = [imageGenerator copyCGImageAtTime:midpoint
                                                         actualTime:&actualTime
                                                              error:&error];
    
    if (centerFrameImage != NULL) {
        image = [[UIImage alloc] initWithCGImage:centerFrameImage];
        // Release the CFRetained image
        CGImageRelease(centerFrameImage);
    }
    
    return image;

1.5 AVPlayerViewControlller

AVPlayerViewController是UIViewController的子类,它可以用来显示AVPlayer对象的视觉内容和标准的播放控制。

具体AVPlayer自定义下面再说,回到AVPlayerViewController,它不支持UI自定义,实现比较简单,代码如下:

    AVPlayer *player = [AVPlayer playerWithURL:self.finalURL];
    AVPlayerViewController *playerVC = [[AVPlayerViewController alloc] init];
    playerVC.player = player;
    playerVC.view.frame = self.view.frame;
    [self.view addSubview:playerVC.view];
    [playerVC.player play];

具体代码可下载Demo


2. 复杂的业务需求,需要自定义视频界面

(1)使用AVFoundation拍照和录制视频,自定义界面

(2)使用AVAssetExportSession转码MP4(一般要兼容Android播放,iOS默认是mov格式)

(3)使用AFNetWorking上传到服务器

(4)网络请求,使用AVFoundation框架的AVPlayer来自定义播放界面,在线播放视频流。播放又分为先下后播和边下边播。

2.1 AVFoundation

AVFoundation是一个可以用来使用和创建基于时间的视听媒体的框架,它提供了一个能使用基于时间的视听数据的详细级别的Objective-C接口。例如:您可以用它来检查,创建,编辑或是重新编码媒体文件。也可以从设备中获取输入流,在视频实时播放时操作和回放。

avfoundation.png

AVCaptureSession:媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出。 AVCaptureDevice :输入设备,包括麦克风、摄像头,通过该对象可以设置物理设备的一些属性(例如相机聚焦、白平衡等)。 AVCaptureDeviceInput :设备输入数据管理对象,可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。 AVCaptureVideoPreviewLayer :相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的 AVCaptureSession对象。

AVCaptureOutput :输出数据管理对象,用于接收各类输出数据,通常使用对应的子类AVCaptureAudioDataOutput、AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput, 该对象将会被添加到AVCaptureSession中管理。

relationship.png

建立视频拍摄的步骤如下:

  1. 创建AVCaptureSession对象。
// 创建会话 (AVCaptureSession) 对象。
_captureSession = [[AVCaptureSession alloc] init];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) {
    // 设置会话的 sessionPreset 属性, 这个属性影响视频的分辨率
    [_captureSession setSessionPreset:AVCaptureSessionPreset640x480];
}
  1. 使用AVCaptureDevice的静态方法获得需要使用的设备,例如拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备。
// 获取摄像头输入设备, 创建 AVCaptureDeviceInput 对象
// 在获取摄像头的时候,摄像头分为前后摄像头,我们创建了一个方法通过用摄像头的位置来获取摄像头 
AVCaptureDevice *videoCaptureDevice = [self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];
if (!captureDevice) {
    NSLog(@"---- 取得后置摄像头时出现问题---- ");
    return;
}

// 添加一个音频输入设备
// 直接可以拿数组中的数组中的第一个
AVCaptureDevice *audioCaptureDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
  1. 利用输入设备AVCaptureDevice初始化AVCaptureDeviceInput对象。
// 视频输入对象
// 根据输入设备初始化输入对象,用户获取输入数据
_videoCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:captureDevice error:&error];
if (error) {
    NSLog(@"---- 取得设备输入对象时出错 ------ %@",error);
    return;
} 

//  音频输入对象
//根据输入设备初始化设备输入对象,用于获得输入数据
_audioCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
if (error) {
    NSLog(@"取得设备输入对象时出错 ------ %@",error);
    return;
}
  1. 初始化输出数据管理对象,如果要拍照就初始化AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象。
// 拍摄视频输出对象
// 初始化输出设备对象,用户获取输出数据
_caputureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
  1. 将数据输入对象AVCaptureDeviceInput、数据输出对象AVCaptureOutput添加到媒体会话管理对象AVCaptureSession中。
// 将视频输入对象添加到会话 (AVCaptureSession) 中
if ([_captureSession canAddInput:_videoCaptureDeviceInput]) {
    [_captureSession addInput:_videoCaptureDeviceInput];
}

// 将音频输入对象添加到会话 (AVCaptureSession) 中
if ([_captureSession canAddInput:_captureDeviceInput]) {
    [_captureSession addInput:audioCaptureDeviceInput];
    AVCaptureConnection *captureConnection = [_caputureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
    // 标识视频录入时稳定音频流的接受,我们这里设置为自动
    if ([captureConnection isVideoStabilizationSupported]) {
        captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
    }
}
  1. 创建视频预览图层AVCaptureVideoPreviewLayer并指定媒体会话,添加图层到显示容器中,调用AVCaptureSession的startRuning方法开始捕获。
// 通过会话 (AVCaptureSession) 创建预览层
_captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];

// 显示在视图表面的图层
CALayer *layer = self.viewContrain.layer;
layer.masksToBounds = true;

_captureVideoPreviewLayer.frame = layer.bounds;
_captureVideoPreviewLayer.masksToBounds = true;
_captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
[layer addSublayer:_captureVideoPreviewLayer];

// 让会话(AVCaptureSession)勾搭好输入输出,然后把视图渲染到预览层上
[_captureSession startRunning];
  1. 将捕获的音频或视频数据输出到指定文件。
//创建一个拍摄的按钮,当我们点击这个按钮就会触发视频录制,并将这个录制的视频放到 temp 文件夹中
- (IBAction)takeMovie:(id)sender {
[(UIButton *)sender setSelected:![(UIButton *)sender isSelected]];
if ([(UIButton *)sender isSelected]) {
     AVCaptureConnection *captureConnection=[self.caputureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
    // 开启视频防抖模式
    AVCaptureVideoStabilizationMode stabilizationMode = AVCaptureVideoStabilizationModeCinematic;
    if ([self.captureDeviceInput.device.activeFormat isVideoStabilizationModeSupported:stabilizationMode]) {
        [captureConnection setPreferredVideoStabilizationMode:stabilizationMode];
    }

    //如果支持多任务则则开始多任务
    if ([[UIDevice currentDevice] isMultitaskingSupported]) {
       self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
    }
    // 预览图层和视频方向保持一致,这个属性设置很重要,如果不设置,那么出来的视频图像可以是倒向左边的。
    captureConnection.videoOrientation=[self.captureVideoPreviewLayer connection].videoOrientation;

   // 设置视频输出的文件路径,这里设置为 temp 文件
    NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:MOVIEPATH];

    // 路径转换成 URL 要用这个方法,用 NSBundle 方法转换成 URL 的话可能会出现读取不到路径的错误
    NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];

   // 往路径的 URL 开始写入录像 Buffer ,边录边写
    [self.caputureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
}
else {
   // 取消视频拍摄
    [self.caputureMovieFileOutput stopRecording];
    [self.captureSession stopRunning];
    [self completeHandle];
}
}

当然我们录制的开始与结束都是有监听方法的,AVCaptureFileOutputRecordingDelegate 这个代理里面就有我们想要做的

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections
{
   NSLog(@"---- 开始录制 ----");
}

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
{
    NSLog(@"---- 录制结束 ----");
}

####2.2 AVPlayer自定义播放器

AVPlayer是用于管理媒体资产的播放和定时的控制器对象,它提供了控制播放器的有运输行为的接口,如它可以在媒体的时限内播放,暂停,和改变播放的速度,并有定位各个动态点的能力。我们可以使用AVPlayer来播放本地和远程的视频媒体文件,如QuickTime影片和MP3音频文件,以及视听媒体使用HTTP流媒体直播服务。

AVPlayer本身并不能显示视频,而且它也不像AVPlayerViewController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。要使用AVPlayer首先了解一下几个常用的类:

  • AVPlayer:播放器,将数据解码处理成为图像和声音。

  • AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。

  • AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。负责网络连接,请求数据。

  • AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,负责数据的获取与分发;一个AVPlayerItem对应着一个视频资源。可以添加观察者监听播放源的3个状态:

    //添加观察者
    [_playerItem 
        addObserver:self 
        forKeyPath:@"status"
        options:NSKeyValueObservingOptionNew 
        context:nil];
    //对播放源的三个状态(status)
    AVPlayerItemStatusReadyToPlay  //播放源准备好
    AVPlayerItemStatusUnknown	   //播放源未知
    AVPlayerItemStatusFailed	   //播放源失败
  • AVPlayerLayer:图像层,AVPlayer的图像要通过AVPlayerLayer呈现。

avplayerLayer_2x.png

使用 AVPlayer 对象控制资产的播放。在播放期间,可以使用一个 AVPlayerItem 实例去管理资产作为一个整体的显示状态,AVPlayerItemTrack 对象来管理一个单独轨道的显示状态。使用 AVPlayerLayer 显示视频。

需要注意的是,AVPlayer的模式是,你不要主动调用play方法播放视频,而是等待AVPlayerItem告诉你,我已经准备好播放了,你现在可以播放了,所以我们要监听AVPlayerItem的状态,通过添加监听者的方式获取AVPlayerItem的状态。当监听到播放器已经准备好播放的时候,就可以调用play方法。

创建一个视频播放器的思路:

  • 创建一个view用来放置AVPlayerLayer
  • 设置AVPlayer AVPlayerItem 并将 AVPlayer放到AVPlayerLayer中,在将AVPlayerLayer添加到[view.layer addSubLayer]中
  • 添加观察者,观察播放源的状态
    1. 如果状态是AVPlayerItemStatusReadyToPlay就开始播放
  • 在做一些功能上的操作
//获取url 本地url
    //NSURL *url = [[NSBundle mainBundle]URLForResource:@"视频" withExtension:@"mp4"];
    NSURL *url = [NSURL URLWithString:@"http://XXX.mp4"];
    //创建playerItem
    _playerItem = [AVPlayerItem playerItemWithURL:url];
    //添加item的观察者 监听播放源的播放状态(status)
    [_playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    //创建playerItem
    _player = [AVPlayer playerWithPlayerItem:_playerItem];
    //创建playerLayer
    _playerLayer=[AVPlayerLayer playerLayerWithPlayer:_player];
    //设置_layer的frame
    _playerLayer.frame=CGRectMake(_playerView.frame.origin.x, _playerView.frame.origin.y, _playerView.frame.size.width,300);
    //添加到_playerView中
    [_playerView.layer addSublayer:_playerLayer];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
  if ([keyPath isEqualToString:@"status"]) {
      AVPlayerItem *playerItem = (AVPlayerItem *)object;
      AVPlayerItemStatus status = playerItem.status;
      switch (status) {
          case AVPlayerItemStatusUnknown:{

          }
              break;
          case AVPlayerItemStatusReadyToPlay:{
              [self.player play];
              self.player.muted = self.mute;
              // 显示图像逻辑
              [self handleShowViewSublayers];

          }
              break;
          case AVPlayerItemStatusFailed:{

          }
              break;
          default:
              break;
      }
  }
}

2.3 边播放,边缓存

如果直接使用上面这种方式, URL 通过 AVPlayer 播放,系统并不会做缓存处理,等下次再播又要重新下载,对流量和网络状态来说也是蛮尴尬的。那如何做到边播放,边缓存,下载完成还能直接从本地读取呢?

答案就是:在本地开启一个 http 服务器,把需要缓存的请求地址指向本地服务器,并带上真正的 url 地址。

cachePlan.png

当我们给播放器设置好url等一些参数后,播放器就会向url所在的服务器发送请求(请求参数有两个值,一个是offset偏移量,另一个是length长度,其实就相当于NSRange一样),服务器就根据range参数给播放器返回数据。这就是AVPlayer播放大致的原理。

AVAssetResourceLoaderDelegate

我们在使用AVURLAsset时,实际上是可以设置AVAssetResourceLoaderDelegate代理。

AVURLAsset *urlAsset = ...
[urlAsset.resourceLoader setDelegate:<AVAssetResourceLoaderDelegate> queue:dispatch_get_main_queue()];

AVAssetResourceLoader是负责数据加载的,最最重要的是我们只要遵守了AVAssetResourceLoaderDelegate,就可以成为它的代理,成为它的代理以后,数据加载可能会通过代理方法询问我们。

只要找一个对象实现了 AVAssetResourceLoaderDelegate 这个协议的方法,丢给 asset,再把 asset 丢给 AVPlayer,AVPlayer 在执行播放的时候就会去问这个 delegate:喂,你能不能播放这个 url 啊?然后会触发下面这个方法:

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest 

AVAssetResourceLoadingRequest 里面,request 代表原始的请求,由于 AVPlayer 是会触发分片下载的策略,还需要从dataRequest 中得到请求范围的信息。有了请求地址和请求范围,我们就可以重新创建一个设置了请求 Range 头的 NSURLRequest 对象,让下载器去下载这个文件的 Range 范围内的数据。

而对应的下载我们可以使用NSURLSession 实现断点下载、离线断点下载等。

// 替代NSMutableURL, 可以动态修改scheme
NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
actualURLComponents.scheme = @"http";

// 创建请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[actualURLComponents URL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20.0];

// 修改请求数据范围
if (offset > 0 && self.videoLength > 0) {
    [request addValue:[NSString stringWithFormat:@"bytes=%ld-%ld",(unsigned long)offset, (unsigned long)self.videoLength - 1] forHTTPHeaderField:@"Range"];
}

// 重置
[self.session invalidateAndCancel];

// 创建Session,并设置代理
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

// 创建会话对象
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];

// 开始下载
[dataTask resume];

我们可以在NSURLSession的代理方法中获得下载的数据,拿到下载的数据以后,为了解决内存暴涨的问题,我们使用NSOutputStream,将数据直接写入到硬盘中存放临时文件的文件夹。在请求结束的时候,我们判断是否成功下载好文件,如果下载成功,就把这个文件转移到我们的存储成功文件的文件夹。如果下载失败,就把临时数据删除。

// 1.接收到服务器响应的时候
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler;

// 2.接收到服务器返回数据的时候调用,会调用多次
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;

// 3.请求结束的时候调用(成功|失败),如果失败那么error有值
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;

代理对象需要实现的功能

  • 1.接收视频播放器的请求,并根据请求的range向服务器请求本地没有获得的数据

  • 2.缓存向服务器请求回的数据到本地

  • 3.如果向服务器的请求出现错误,需要通知给视频播放器,以便视频播放器对用户进行提示

视频播放器处理流程

  • 1.当开始播放视频时,通过视频url判断本地cache中是否已经缓存当前视频,如果有,则直接播放本地cache中视频
  • 2.如果本地cache中没有视频,则视频播放器向代理请求数据
  • 3.加载视频时展示正在加载的提示(菊花转)
  • 4.如果可以正常播放视频,则去掉加载提示,播放视频,如果加载失败,去掉加载提示并显示失败提示
  • 5.在播放过程中如果由于网络过慢或拖拽原因导致没有播放数据时,要展示加载提示,跳转到第4步

代理对象处理流程

  • 1.当视频播放器向代理请求dataRequest时,判断代理是否已经向服务器发起了请求,如果没有,则发起下载整个视频文件的请求
  • 2.如果代理已经和服务器建立链接,则判断当前的dataRequest请求的offset是否大于当前已经缓存的文件的offset,如果大于则取消当前与服务器的请求,并从offset开始到文件尾向服务器发起请求(此时应该是由于播放器向后拖拽,并且超过了已缓存的数据时才会出现)
  • 3.如果当前的dataRequest请求的offset小于已经缓存的文件的offset,同时大于代理向服务器请求的range的offset,说明有一部分已经缓存的数据可以传给播放器,则将这部分数据返回给播放器(此时应该是由于播放器向前拖拽,请求的数据已经缓存过才会出现)
  • 4.如果当前的dataRequest请求的offset小于代理向服务器请求的range的offset,则取消当前与服务器的请求,并从offset开始到文件尾向服务器发起请求(此时应该是由于播放器向前拖拽,并且超过了已缓存的数据时才会出现)
  • 5.只要代理重新向服务器发起请求,就会导致缓存的数据不连续,则加载结束后不用将缓存的数据放入本地cache
  • 6.如果代理和服务器的链接超时,重试一次,如果还是错误则通知播放器网络错误
  • 7.如果服务器返回其他错误,则代理通知播放器网络错误

###3. 在线直播和视频点播,开发流程:

AVPlayer + ffmpeg / ijkplayer

我们平时看到的视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为容器Container), 不同的容器格式规定了其中音视频数据的组织方式(也包括其他数据,比如字幕等)。容器中一般会封装有视频和音频轨,也称为视频流(stream)和音频 流,播放视频文件的第一步就是根据视频文件的格式,解析(demux)出其中封装的视频流、音频流以及字幕(如果有的话),解析的数据读到包 (packet)中,每个包里保存的是视频帧(frame)或音频帧,然后分别对视频帧和音频帧调用相应的解码器(decoder)进行解码,比如使用 H.264编码的视频和MP3编码的音频,会相应的调用H.264解码器和MP3解码器,解码之后得到的就是原始的图像(YUV or RGB)和声音(PCM)数据,然后根据同步好的时间将图像显示到屏幕上,将声音输出到声卡,最终就是我们看到的视频。

FFmpeg的API就是根据这个过程设计的,ffmpeg是一个多平台多媒体处理工具,处理视频和音频的功能非常强大,能够解码,编码, 转码,复用,解复用,流,过滤器和播放大部分的视频格式。ffmpeg的代码是包括两部分的,一部分是library,一部分是tool。api都是在library里面,如果直接调api来操作视频的话,就需要写c或者c++了。另一部分是tool,使用的是命令行,则不需要自己去编码来实现视频操作的流程。实际上tool只不过把命令行转换为api的操作而已。

ijkplayer

ijkplayer 框架是B站(BiliBili)提供了一个开源的流媒体解决方案,集成了 ffmpeg。使用 ijkplayer 框架我们可以很方便地实现视频直播功能(HTTP/RTMP/RTSP 这几种直播源都支持)。并且同时支持 iOS 和 Android 。


一个完整直播app实现流程

1.采集、2.滤镜处理、3.编码、4.推流、5.CDN分发、6.拉流、7.解码、8.播放、9.聊天互动

live_broadcast.png

具体到各个环节:推流端(采集、美颜处理、编码、推流)、服务端处理(转码、录制、截图、鉴黄)、播放器(拉流、解码、渲染)、互动系统(聊天室、礼物系统、赞)

一个完整直播app技术点

technical_point.jpg

流媒体开发:网络层(socket或st)负责传输,协议层(rtmp或hls)负责网络打包,封装层(flv、ts)负责编解码数据的封装,编码层(h.264和aac)负责图像,音频压缩。

:每帧代表一幅静止的图像

GOP
  • 直播的数据,其实是一组图片,包括I帧、P帧、B帧,当用户第一次观看的时候,会寻找I帧,而播放器会到服务器寻找到最近的I帧反馈给用户。因此,GOP Cache增加了端到端延迟,因为它必须要拿到最近的I帧
  • GOP Cache的长度越长,画面质量越好

码率:图片进行压缩后每秒显示的数据量。

帧率
  • 由于人类眼睛的特殊生理结构,如果所看画面之帧率高于16的时候,就会认为是连贯的,此现象称之为视觉暂留。并且当帧速达到一定数值后,再增长的话,人眼也不容易察觉到有明显的流畅度提升了。

分辨率:(矩形)图片的长度和宽度,即图片的尺寸

压缩前的每秒数据量:帧率X分辨率(单位应该是若干个字节)

压缩比:压缩前的每秒数据量/码率 (对于同一个视频源并采用同一种视频编码算法,则:压缩比越高,画面质量越差。)

视频文件格式文件的后缀,比如.wmv,.mov,.mp4,.mp3,.avi,

  • 主要用处,根据文件格式,系统会自动判断用什么软件打开, 注意: 随意修改文件格式,对文件的本身不会造成太大的影响,比如把avi改成mp4,文件还是avi.

视频封装格式一种储存视频信息的容器,流式封装可以有TS、FLV等,索引式的封装有MP4,MOV,AVI等,

  • 主要作用:一个视频文件往往会包含图像和音频,还有一些配置信息(如图像和音频的关联,如何解码它们等):这些内容需要按照一定的规则组织、封装起来.
  • 注意:会发现封装格式跟文件格式一样,因为一般视频文件格式的后缀名即采用相应的视频封装格式的名称,所以视频文件格式就是视频封装格式。

视频封装格式和视频压缩编码标准:就好像项目工程和编程语言,封装格式就是一个项目的工程,视频编码方式就是编程语言,一个项目工程可以用不同语言开发。


另这里有几款优秀的播放器控件可去GitHub下载学习:

ZFPlayer - 使用人数也很多。基于AVPlayer,一个播放器的基本功能几乎很全面了

mobileplayer-ios - 纯 Swift 。文档详细,功能齐全

KRVideoPlayer - 36kr 开源项目。类似Weico的播放器

SGPlayer - 支持 VR 播放

VKVideoPlayer - VKVideoPlayer is the same battle tested video player used in our Viki iOS App enjoyed by millions of users all around the world

WMPlayer - AVPlayer的封装,继承UIView,想怎么玩就怎么玩。支持播放mp4、m3u8、3gp、mov,网络和本地视频同时支持。全屏和小屏播放同时支持。自动感应旋转屏

Player - ▶️ video player in Swift, simple way to play and stream media on iOS/tvOS


参考文献:

AVAsset

AVFoundation Programming Guide(官方文档翻译)完整版

iOS录制视频

iOS录制(或选择)视频,压缩、上传(整理)

iOS视频录制压缩

iOS上传视频到服务器

iOS视频边下边播--缓存播放数据流

IOS 微信聊天发送小视频的秘密(AVAssetReader+AVAssetReaderTrackOutput播放视频)

可能是目前最好的 AVPlayer 音视频缓存方案

[iOS]仿微博视频边下边播之封装播放器

【补充】NSURLSession 详解离线断点下载的实现

[iOS: FFmpeg的使用一](http://www.cnblogs.com/XYQ-208910/p/5651166.html)

iOS 利用FFmpeg 开发音视频流(二)——Mac 系统上编译 iOS 可用的FFmpeg 库

iOS中集成ijkplayer视频直播框架

iOS 直播 —— 推流

【如何快速的开发一个完整的iOS直播app】(原理篇)

iOS流媒体开发之三:HLS直播(M3U8)回看和下载功能的实现