第五十八篇、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:];
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 > ) {
// 读取 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, );
// 得到 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, , bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
//根据这个位图 context 中的像素创建一个 Quartz image 对象
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// 解锁 pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer, );
// 释放 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);
第五十八篇、iOS 微信聊天发送小视频的秘密的更多相关文章
- 第五十八篇:webpack的Source Map
好家伙,Source Map没听过 1.什么是Source Map? 字面意义上来看应该是个好东西 Source Map 就是一个信息文件,里面储存着位置信息. 也就是说,Source Map 文件中 ...
- Python之路【第十八篇】:Web框架们
Python之路[第十八篇]:Web框架们 Python的WEB框架 Bottle Bottle是一个快速.简洁.轻量级的基于WSIG的微型Web框架,此框架只由一个 .py 文件,除了Pytho ...
- 第八篇 :微信公众平台开发实战Java版之如何网页授权获取用户基本信息
第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...
- Egret入门学习日记 --- 第十八篇(书中 8.5~8.7 节 内容)
第十八篇(书中 8.5~8.7 节 内容) 其实语法篇,我感觉没必要写录入到日记里. 我也犹豫了好久,到底要不要录入. 这样,我先读一遍语法篇的所有内容,我觉得值得留下的,我就录入日记里. 不然像昨天 ...
- 《手把手教你》系列技巧篇(五十八)-java+ selenium自动化测试-分页测试(详细教程)
1.简介 前几天,有人私信里留言问宏哥,分页怎么自动化测试了,完了给他说了说思路,不知道最后搞定没有,索性宏哥就写一篇文章来讲解和介绍如何处理分页. 2.测试场景 对分页来说,我们最感兴趣的和测试的无 ...
- Android UI开发第二十八篇——Fragment中使用左右滑动菜单
Fragment实现了Android UI的分片管理,尤其在平板开发中,好处多多.这一篇将借助Android UI开发第二十六篇——Fragment间的通信. Android UI开发第二十七篇——实 ...
- (转)OpenFire源码学习之十八:IOS离线推送
转:http://blog.csdn.net/huwenfeng_2011/article/details/43458213 IOS离线推送 场景: 如果您有iOS端的APP,在会话聊天的时候,用户登 ...
- iOS燃烧动画、3D视图框架、天气动画、立体相册、微信朋友圈小视频等源码
iOS精选源码 iOS天气动画,包括太阳,云,雨,雷暴,雪动画. 较为美观的多级展开列表 3D立体相册,可以旋转的立方体 一个仪表盘Demo YGDashboardView 一个基于UIScrollV ...
- 【第十二篇】微信支付(APP)集成时碰到的问题(.net提示“无权限”、iOS跳转到微信支付页面中间只有一个“确定”按钮)(转)
直入主题之前,请容我吐槽一下微*的官方东西:ASDFQ%#$%$#$%^FG@#$%DSFQ#$%.......:吐槽玩了!大家心照就好. 要完成手机APP跳转到微信的APP进行微信支付,需要进行如下 ...
随机推荐
- extjs grid renderer用法【转载】
今天在做项目时,需要在列表中的某列添加一个超链接,首先要取得当前选中行的数据,判断数据类型,然后链接到不同的页面,研究下.发现ExtJs提供了一个很强的方法如下: var cm = new Ext.g ...
- Template模式
在开发时,有时会遇到对于一个算法的实现,在不同的对象中有不同的实现,可是这个算法的框架是同样的.这时能够使用Template模式或Strategy模式. Template是採用继承的方式来实现这一点, ...
- .NET中的三种Timer的区别和用法
最近正好做一个WEB中定期执行的程序,而.NET中有3个不同的定时器.所以正好研究研究.这3个定时器分别是: //1.实现按用户定义的时间间隔引发事件的计时器.此计时器最宜用于 Windows 窗体应 ...
- HDU 3410 && POJ 3776 Passing the Message 单调队列
题意: 给定n长的数组(下标从1-n)(n个人的身高,身高各不同样 问:对于第i个人,他能看到的左边最矮的人下标.(假设这个最矮的人被挡住了,则这个值为0) 还有右边最高的人下标,同理若被挡住了则这个 ...
- decide your linux OS is GUI or not
Try: ps -ef|grep X The ps command will display information about a selection of the active process ...
- Delphi 7 升级到 Delphi 2010 总结
1 字符串 >>string =unicodeString 字母的处理要定义AnsiString了 >>PChar =PWidechar >>str='普通汉字' ...
- Android Activity界面切换添加动画特效
在Android 2.0之后有了overridePendingTransition() ,其中里面两个参数,一个是前一个activity的退出两一个activity的进入, @Override pub ...
- JAVA(2)——JDBC
刚接触JDBC的时候,有时候就在想,为什么java要用JDBC,而不是直接使用之前在VB中就学习过的ODBC,由于ODBC是在JDBC之前出现,所以ODBC肯定由于无法完毕某些操作或者不能非常顺利的完 ...
- C++之运算符重载(2)
上一节主要讲解了C++里运算符重载函数,在看了单目运算符(++)重载的示例后,也许有些朋友会问这样的问题.++自增运算符在C或C++中既可以放在操作数之前,也可以放在操作数之后,但是前置和后置的作用又 ...
- SparkStreamingTest.scala
/** * Created by root on 9/8/15. */ import org.apache.spark._ import org.apache.spark.rdd.RDD import ...