该文章引用自: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

AVAssetReader+AVAssetReaderTrackOutput播放视频的更多相关文章

  1. Android实现播放视频

    转载:http://www.bdqn.cn/news/201311/12100.shtml 使用VideoView播放视频 VideoView,用于播放一段视频媒体,它继承了SurfaceView,位 ...

  2. Android使用TextureView播放视频

    1.引言 如果你想显示一段在线视频或者任意的数据流比如视频或者OpenGL 场景,你可以用android中的TextureView做到. 1).TextureView的兄弟SurfaceView 应用 ...

  3. WPF播放视频

    在现在的项目中需要使用到播放视频的功能,本来打算使用VLC来做的.后来发现WPF 4.0之后新增了MediaElement类,可以实现视频播放. <Grid> <Grid.RowDe ...

  4. Cocos2dx3.11.1Android播放视频,后台 黑屏,无法记忆播放bug修改

    /* * Copyright (C) 2006 The Android Open Source Project * Copyright (c) 2014 Chukong Technologies In ...

  5. 不完全解决Android微信HTML5 播放视频的问题(不显示控制条,可交互)

    首先你需要知道以下内容: http://ad.weixin.qq.com/learn/2-3-3--%E9%80%9A%E7%94%A8%E5%BA%93 这是微信为广告商开放的API,我一直认为只有 ...

  6. Bootstrap 3 模态框播放视频

    Bootstrap 3 模态框播放视频 I'm trying to use the Modal feature from Bootstrap 3 to show my Youtube video. I ...

  7. android SurfaceView中播放视频 按视频的原始比例播放

    OnPreparedListener mediaPlayerOnPreparedListener = new OnPreparedListener() { @Override public void ...

  8. Android WebView 总结 —— 使用HTML5播放视频及全屏方案

    在APP开发的过程中,会碰到需要在WebView中播放视频的需求,下面讲解一下如何在WebView中使用html5播放视频. 1.让视频在各个Android版本能够正常播放 在AndroidManif ...

  9. Android三种播放视频的方式

    在Android中,我们有三种方式来实现视频的播放: 1.使用其自带的播放器.指定Action为ACTION_VIEW,Data为Uri,Type为其MIME类型. 2.使用VideoView来播放. ...

随机推荐

  1. 5天揭秘js高级技术-第一天

    一.基础杂记 1. document.write() <script type="text/javascript"> document.write('<h2> ...

  2. Eclipse MySQL Hibernate 中文乱码问题

    如果是Eclipse的问题,那就对以下各项进行设置,即可排除问题. 设置eclipse相关编码为UTF-8: 修改工作区默认编码,位置:windows--perferences--general--w ...

  3. C++网络套接字编程TCP和UDP实例

    原文地址:C++网络套接字编程TCP和UDP实例作者:xiaojiangjiang 1.       创建一个简单的SOCKET编程流程如下 面向有连接的套接字编程 服务器: 1)  创建套接字(so ...

  4. django(一)

    Django 自称是"最适合开发有限期的完美WEB框架".本文参考<Django web开发指南>,快速搭建一个blog 出来,在中间涉及诸多知识点,这里不会详细说明, ...

  5. 深入浅析JAVA注解

    注解,相信大家都会知道,像@requestMapping,@Resource,@Controller等等的一些注解,大家都用过,那么,他的工具类你用过吗?下面就和大家一起来分享一下注解工具类. 注解的 ...

  6. ES6学习笔记一

    块级作用域:在ES5中只有全局作用域与函数作用域,ES6中新增的块级作用域避免变量的覆盖与泄露.考虑到代码运行环境,在块级作用域中应避免声明函数,最好用函数表达式的方式声明. let与const:le ...

  7. linux第二天

    由于今天公司事情比较多,今天基本没有自学.了解了一下type命令:主要是显示命令类型的命令.linux命令分为内置命令和外部命令两种.内置命令就shell内置的命令,外部命令,就是需要有系统存在一个名 ...

  8. 小明的密码-初级DP解法

    #include #include #include using namespace std; int visited[5][20][9009];// 访问情况 int dp[5][20][9009] ...

  9. 【转】linux查看及修改文件权限以及相关

    linux查看及修改文件权限以及相关 查看文件权限的语句: 在终端输入: ls -l xxx.xxx (xxx.xxx是文件名) 那么就会出现相类似的信息,主要都是这些: -rw-rw-r-- 一共有 ...

  10. PhotoSwipe插件的使用

    1.首先引入插件 <link rel="stylesheet" href="css/photoswipe.css"> <link rel=&q ...