zoukankan      html  css  js  c++  java
  • AVAssetReader+AVAssetReaderTrackOutput播放视频

     

    该文章引用自:http://www.jianshu.com/p/3d5ccbde0de1

     

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

    对于播放视频,大家应该一开始就想到比较方便快捷使用简单的MPMoviePlayerController类,确实用这个苹果官方为我们包装好了的 API 确实有很多事情都不用我们烦心,我们可以很快的做出一个视频播放器,但是很遗憾,高度封装的东西,就证明了可自定义性越受限制,而MPMoviePlayerController却正正证明了这一点。所以大家又相对的想起了AVPlayer,是的,AVPlayer是一个很好的自定义播放器,但是,AVPlayer却有着性能限制,微信团队也证实这一点,AVPlayer只能同事播放16个视频,之后创建一个视频,对可滚动的聊天界面来说,是一个非常致命的性能限制了。

    AVAssetReader+AVAssetReaderTrackOutput

    那么既然AVPlayer有着性能限制,我们就做一个属于我们的播放器吧,AVAssetReader可以从原始数据里获取解码后的音视频数据。结合AVAssetReaderTrackOutput ,能读取一帧帧的CMSampleBufferRef 。CMSampleBufferRef 可以转化成CGImageRef 。为此,我们可以创建一个ABSMovieDecoder 的一个类来负责视频解码,把读出的每一个CMSampleBufferRef 传递给上层。

    那么用ABSMovieDecoder- (void)transformViedoPathToSampBufferRef:(NSString *)videoPath方法利用AVAssetReader+AVAssetReaderTrackOutput解码的步骤如下:
    1.获取媒体文件的资源AVURLAsset

    // 获取媒体文件路径的 URL,必须用 fileURLWithPath: 来获取文件 URL
    NSURL *fileUrl = [NSURL fileURLWithPath:videoPath];
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileUrl options:nil];
    NSError *error = nil;
    AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];

    2.创建一个读取媒体数据的阅读器AVAssetReader

    AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];

    3.获取视频的轨迹AVAssetTrack其实就是我们的视频来源

    NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
    AVAssetTrack *videoTrack =[videoTracks objectAtIndex:0];

    4.为我们的阅读器AVAssetReader进行配置,如配置读取的像素,视频压缩等等,得到我们的输出端口videoReaderOutput轨迹,也就是我们的数据来源

     int m_pixelFormatType;
    //     视频播放时,
    m_pixelFormatType = kCVPixelFormatType_32BGRA;
    // 其他用途,如视频压缩
    //    m_pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
    
    NSMutableDictionary *options = [NSMutableDictionary dictionary];
    [options setObject:@(m_pixelFormatType) forKey:(id)kCVPixelBufferPixelFormatTypeKey];
    AVAssetReaderTrackOutput *videoReaderOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:options];

    5.为阅读器添加输出端口,并开启阅读器

    [reader addOutput:videoReaderOutput];
    [reader startReading];

    6.获取阅读器输出的数据源 CMSampleBufferRef

    // 要确保nominalFrameRate>0,之前出现过android拍的0帧视频
    while ([reader status] == AVAssetReaderStatusReading && videoTrack.nominalFrameRate > 0) {
        // 读取 video sample
        CMSampleBufferRef videoBuffer = [videoReaderOutput copyNextSampleBuffer];
        [self.delegate mMoveDecoder:self onNewVideoFrameReady:videoBuffer];
    
        // 根据需要休眠一段时间;比如上层播放视频时每帧之间是有间隔的,这里的 sampleInternal 我设置为0.001秒
        [NSThread sleepForTimeInterval:sampleInternal];
    }

    7.通过代理告诉上层解码结束

    // 告诉上层视频解码结束
    [self.delegate mMoveDecoderOnDecoderFinished:self];

    至此,我们就能获取视频的每一帧的元素CMSampleBufferRef,但是我们要把它转换成对我们有用的东西,例如图片

    // AVFoundation 捕捉视频帧,很多时候都需要把某一帧转换成 image
    + (CGImageRef)imageFromSampleBufferRef:(CMSampleBufferRef)sampleBufferRef
    {
      // 为媒体数据设置一个CMSampleBufferRef
      CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBufferRef);
      // 锁定 pixel buffer 的基地址
      CVPixelBufferLockBaseAddress(imageBuffer, 0);
      // 得到 pixel buffer 的基地址
      void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
      // 得到 pixel buffer 的行字节数
      size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
      // 得到 pixel buffer 的宽和高
      size_t width = CVPixelBufferGetWidth(imageBuffer);
      size_t height = CVPixelBufferGetHeight(imageBuffer);
    
      // 创建一个依赖于设备的 RGB 颜色空间
      CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    
      // 用抽样缓存的数据创建一个位图格式的图形上下文(graphic context)对象
      CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
      //根据这个位图 context 中的像素创建一个 Quartz image 对象
      CGImageRef quartzImage = CGBitmapContextCreateImage(context);
      // 解锁 pixel buffer
      CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
    
      // 释放 context 和颜色空间
      CGContextRelease(context);
      CGColorSpaceRelease(colorSpace);
      // 用 Quzetz image 创建一个 UIImage 对象
      // UIImage *image = [UIImage imageWithCGImage:quartzImage];
    
      // 释放 Quartz image 对象
      //    CGImageRelease(quartzImage);
    
      return quartzImage;
    
    }

    从上面大家可以可得出,获取图片图片的最直接有效的是 UIImage 了,但是为什么我不需要 UIImage 却要了个撇足的 CGImageRef 呢? 那是因为创建CGImageRef不会做图片数据的内存拷贝,它只会当 Core Animation执行 Transaction::commit() 触发 layer -display时,才把图片数据拷贝到 layer buffer里。简单点的意思就是说不会消耗太多的内存!


    接下来我们需要把所有得到的CGImageRef元素都合成视频了。当然在这之前应该把所有的 CGImageRef 当做对象放在一个数组中。那么知道CGImageRef为 C 语言的结构体,这时候我们要用到桥接来将CGImageRef转换成我们能用的对象了

    CGImageRef cgimage = [UIImage imageFromSampleBufferRef:videoBuffer];
    if (!(__bridge id)(cgimage)) { return; }
    [images addObject:((__bridge id)(cgimage))];
    CGImageRelease(cgimage);

    - (void)mMoveDecoderOnDecoderFinished:(TransformVideo *)transformVideo
    {
      NSLog(@"视频解档完成");
      // 得到媒体的资源
      AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:filePath] options:nil];
      // 通过动画来播放我们的图片
      CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
      // asset.duration.value/asset.duration.timescale 得到视频的真实时间
      animation.duration = asset.duration.value/asset.duration.timescale;
      animation.values = images;
      animation.repeatCount = MAXFLOAT;
      [self.preView.layer addAnimation:animation forKey:nil];
      // 确保内存能及时释放掉
      [images enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
          if (obj) {
              obj = nil;
          }
      }];
    }

    @end

  • 相关阅读:
    10-关于DOM的事件操作
    09-伪数组 arguments
    08-函数
    07-常用内置对象
    Django -- 2.http协议
    Django -- 1.web应用
    web前端 --- JavaScrip之BOM
    web前端 --- JavaScrip函数与对象
    web前端 --- JavaScrip基础
    web前端 --- CSS下篇
  • 原文地址:https://www.cnblogs.com/tangranyang/p/6259593.html
Copyright © 2011-2022 走看看