• 第五十八篇、iOS 微信聊天发送小视频的秘密


    对于播放视频,大家应该一开始就想到比较方便快捷使用简单的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);
  • 相关阅读:
    【BZOJ5286】[HNOI2018]转盘(线段树)
    【BZOJ2003】[HNOI2010]矩阵(搜索)
    【BZOJ2000】[HNOI2000]取石头游戏(贪心,博弈论)
    【BZOJ1998】[HNOI2010]物品调度(并查集,模拟)
    【BZOJ2001】[HNOI2010]城市建设(CDQ分治,线段树分治)
    【BZOJ1925】[SDOI2010]地精部落(动态规划)
    【BZOJ1856】[SCOI2010]字符串(组合数学)
    【BZOJ1826】[JSOI2010]缓存交换(贪心)
    【BZOJ1823】[JSOI2010]满汉全席(2-sat)
    【BZOJ1822】[JSOI2010]冷冻波(二分,网络流)
  • 原文地址:https://www.cnblogs.com/HJQ2016/p/5964327.html
Copyright © 2020-2023  润新知