考研辅助app的诞生!
背景: 去年下半年由于种种因素驱动下,准备去考研,在之前同事的推荐下,参加了考研培训班,培训班发了纸质书籍和线上视频观看账号,由于线上视频需要全程联网才能观看,突发奇想,要是我把这些视频下载下来,没网的时候也能拿出来观看复习多好; 在此背景下,花了几天时间简单做了一个app出来辅助复习,前段时间通过了考研复试,这几天利用空闲时间,完善了app功能,同时记录下开发过程和一些感悟;
效果:
功能简单介绍: 在线播放(下载).m3u8视频,pc浏览器查看和离线观看!
注意: 由于涉及到培训班视频的隐私问题,这里不给出视频播放画面,敬请谅解!
如何实现?
市面上众多培训班的视频一般都不是普通的.mp4视频,很多都是经过了加密或者非专业人士知道的特殊视频格式, 工作期间经常使用抓包工具,我想先试下抓包看看,说不定可以了解到一些信息,从而启发下一步,好,说干就干....
在用charles抓包的时候,发现截图该域名下不断的有新请求产生,有一个.m3u8请求地址,还有不断产生的.ts.ts请求地址, 网上搜索相关文章,茅塞顿开,实现起来并不复杂。
涉及到的技术实现网上文章很多,当时备考时间短,我把自己的核心需求理了下,网上找相关"*",有满足自己的需要就直接拿来组装了 ,哈哈
技术要点
一.ios音视频播放
ios音视频播放技术现在很成熟了,之前在项目中有涉及到视频播放相关处理的功能,代码中主要使用的类是avplayer,个人建议首先看苹果文档,最全面最权威的了,网上找到的不够全面,要综合不同文章才能全面些,打开位置xcode-window-developer documentation,搜索类名
官方说明该类可用来处理本地和远程基于文件的媒体播放,例如quiktime电影,mp3音频文件,以及使用hls直播提供的视听媒体; 使用该对象就可以直接播放前面说的.m3u8文件了;
不过这样会存在一个问题,当网络不好的时候,体验就很糟糕了,而且也不能离线观看; 看能不能处理成常用的.mp4文件,方便保存和离线观看?
基本思路是: 批量下载.ts.ts文件=》合并文件=>转码成.mp4
批量下载ts.ts文件就基本的文件网络下载思路了
合并文件: 用一个nsmutabledata来存储合并后的文件数据,循环读取每一个.ts片段数据,追加到nsmutabledata
转码成.mp4: 这一步需要用到ffmpeg框架来做,对应的ios库,一般可采用命令行的方式进行调用,这里的转码示例如下:
调用方式即:
ffmpeg -ss 00:00:00 -i 源文件.ts文件路径 -b:v 2000k -y 目标.mp4文件路径
存在一个问题,我转码出来,发现.mp4视频文件出奇的大,724mb,而.ts源文件才70mb左右,如果我把培训班的视频都下载下来了,那占用手机空间就会很大,我用的手机是128gb,剩余空间也就不到15gb 。
我把参数都去掉,用默认的
ffmpge -i <in file> <output file>
转码出来是180mb左右,不同命令参数转码出来的文件大小相差很大,这涉及到对ffmpeg的理解和一些音视频的概念基础知识了。
关于.ts转.mp4的各种命令也很多,我上网找到如下命令行:
覆盖目标文件,使用h264_mp4toannexb,使用源文件声音编解码器,视频编解码器
ffmpeg -y -i <in file> -vcodec copy -acodec copy -vbsf h264_mp4toannexb <output file>
源文件开头到末尾,目标文件视频的码率设置为2000kbps ffmpeg -ss 00:00:00 -i <in file> -b:v 2000k -y <output file>
ffmpeg -y -i <in file> -c:v libx264 -c:a copy -bsf:a aac_adtstoasc <output file>
ios里使用ffmpeg命令行的代码,涉及到一些objective-c调c语言的方式,分享下
//输入源文件沙盒路径 char* input=(char*) [inputpath utf8string]; //输出目标文件沙盒路径 char* output= (char*)[outpath utf8string]; //ffmpeg命令 char* a[]={"ffmpeg","-y","-i",input,output}; //开始调起ffmpeg ffmpeg_main(sizeof(a)/sizeof(*a),a);
基本概念
1.视频和视频格式的说明
视频是n多张图片的集合,播放视频本职其实就是连续播放"很多"张图片,其播放每张图片的时间间隔非常的短,我们把每一张图片称为每一帧,每一帧图片有多少像素,称为这张图像的分辨率,比如我们有一个1.mp4视频文件,它的分辨率就是每一帧图像的分辨率;
拿到一个视频文件,我们除了知道常见的分辨率和封装格式外,诸如帧率,码率最好也了解一下; 帧率说明视频单位时间1秒内可以播放多少张图像,视频的帧率可以是恒定的,也可以是动态的,码率说明单位时间内记录视频数据的总量,比如一个24分钟,900mb的视
频,其码率为7200mbit/1440s=5000kbps=5mbps,单位一般是kbit/s或者mbit/s,一个视频里不仅有图像还有音频,码率是两者的总和,反过来通过码率,我们也很容易知道一个视频文件的大小,其计算公式为(音频编码率+视频编码率)kbps/8*视频长度s
我们知道一张图片如果不编码,其占用字节空间很大,比如一张图片像素为1024*1024,位深为32位,则图片占用空间为: 1024*1024*32/8=4096kb ,如果一个视频单纯的把所有图片占用的空间加起来,那么其占用空间会出奇的大,这样对于存储和传输都是很大问题的。
我们日常看到的.mp4后缀的视频文件都是把视频流,音频流编码后通过mp4格式封装好的文
件,看起来文件占用并不大,mp4是一种视频封装格式,我们还会看见诸如.mkv,.avi等视频封装格式。
前面说到,需要对视频流数据进行编码,减少占用空间,最著名的视频编码格式就是h.264了.
2.视频是如何被播放出来的?
本app对.mp4文件进行离线播放,原理是如何的,我是直接拿"*"搭的,这个*是sjvideoplayer ,其内部对视频播放的原理直接使用的是苹果官方avplayer,在其基础上进行封装的,这里不对封装做研究,了解下其avplayer能做什么?
之前在做广告业务的时候,视频这一块也是用这个处理的,受限于业务需要,使用还不够深,这里对sjvideoplayer分析,了解下其的强大之处!
a. 加载本地视频文件,进行开头和定点开始播放,任意构建自定义播放尺寸,添加到uiview上。
b. 支持续播和旋转,旋转固定
c. 常规播放控制
站在巨人的肩膀上太久,高处未免不胜寒,有点发慌,来了解下底层播放的知识先!
拿到一个mp4视频文件,对其进行播放,需要一个解码的过程,跟图片一样,都需要解码数据。大致的播放流程如下:
3.hls(http直播)
hls是苹果的动态码率自适应技术,基于http的流媒体网络传输协议,它的工作原理是把整个流分成一个个小的基于http的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。支持的视频流编码为h.264。我们在视频网站上看到的m3u8后缀的播放链接就是使用hls协议的视频,它包括一个m3u8的索引文件,ts媒体分片和key加密串文件(该加密串文件可有可无)。
hls具有下述优点:
a.用户可以看完一段缓存一段,防止只看一段视频但是把整个视频文件都缓存下来,减少服务器压力和流量消耗。 (这里让我想到之前在做广告业务的时候,在视频广告这块,跟市面上很多广告sdk都类似,使用的是.mp4视频格式,其实是难满足这种场景的,如果被聚合在一起的时候,各家的视频缓存效率就是一项可pk的点,后续可思考这一块~)
补充一下: hls可以做加密保证文件的安全性和防止被盗用
1. 常见的一种是防盗链(严格来讲这不属于加密) , 也就是说给 m3u8 和 ts 文件的url动态生成一个 token , 比如这个:
http://www.cuplayer.com/m3u8/hunan/desc.m3u8?stream_id=hunan<m=1410595018&lkey=8bc1e0fe35f6321ef560f8ccffb70e5d&path=59.49.42.14,58.59.3.9,58.59.3.51&platid=10&splatid=1015&tag=live&cips=127.0.0.1&ext=m3u8&sign=live_tv
这个url是随着很多参数动态变化的,比如时间,用户id、ip地址,内容id , 导致你无法使用这个url盗链,这种方式可以防止其他网站直接使用你的url来观看或者一般用户的下载。
而ts文件的url 也需要加请求token , 会变化成类似 http://server/file.ts?token=xxxx 的方式, 这样的话, ts文件的磁盘存储位置不用变化,但是url是可以变化的(可以用query string方式,也可以用 url rewrite 方式), 注意因为url是m3u8生成的,意味着m3u8文件是动态生成而并非静态文件
关于cdn缓存的问题, 首先m3u8文件肯定不能缓存, 否则ts分片的动态url怎么办?
然后动态url的 ts分片cdn缺省是肯定不缓存的,但是可以稍微定制一下让cdn忽略url中的token部分。很多cdn都有自己的防盗链方案。
2. drm加密。防盗链的方式是一种一般性的保护, 假如你想完全保护你的内容,必须给ts内容加密, m3u8有这个tag: #ext-x-key , 一般来说会提供一个url获取加密key, 然后对ts片段解密来播放文件
加密:
解密:
视频加密方案:
b.根据网络带宽切换不同的码率,兼顾速度和清晰度。
4.m3u8是个啥?
抓包分析请求链接,是以后缀.m3u8 content-type是“application/vnd.apple.mpegurl”的文件
其文件内容为.ts片段列表,每一个.ts片段对应视频流的不同数据,全部.ts片段组成了视频数据,客户端不断的去下载里面的片段,由于片段之间的分段间隔非常短,最后看起来就是一条完整的播放流,也就是正在播放的视频。
学员的手机以类似轮询的方式不断重复加载该.m3u8文件并将.ts片段追加实现流媒体的播放。
对.m3u8文件内容了解下:
#ext-x-targetduration: 表示每一个.ts片段的最大时长
#extinf: 表示下面.ts片段的时长
#ext-x-version:表示该播放列表所兼容的所有版本
#ext-x-endlist:表明m3u8文件的结束
还有一些我示例里没公布的标记说明可参照官方说明: ,这里有一篇中文翻译版本:
我抓包的视频是已经录制好的视频,该播放列表的数据内容格式是单码率视频适配流,而有时候不同用户的网络带宽不一样,下发的.m3u8文件里的内容可能有不同码率的文件,用户手机会选择一个适合自己的文件进行播放,这样来保证直播视频流的流畅; 多码率视频流会多出下面标记:
#ext-x-stream-inf: 表示播放列表中的下一个url file,来标识另一个播放列表文件
该标记包含以下属性
bandwidth 指定码率,用于不同网络带宽自适应选择对应的码流播放
program-id 唯一id
codecs 指定流的编码类型
示例如下:
#extm3u
#ext-x-stream-inf:program-id=1,bandwidth=1280000
http://example.com/1.m3u8
#ext-x-stream-inf:program-id=1,bandwidth=1280000
http://example.com/2.m3u8
客户端会首选选择码率最高的.m3u8请求
单码率的如下:
#extm3u
#ext-x-targetduration:5220
#extinf:5220,
http://media.example.com/1.ts
#ext-x-endlist
我这里碰到的.m3u8全部都是没有加密过的,加密的一般会多出下图红框里的内容
以后找时间看此类视频如何处理吧。
5. ffmpeg
ffmpeg是一个跨平台的视音频录制、转换和流媒体化的解决方案, 这里需要用到视频格式转换的功能。
ffmpeg在ios平台的使用,网上文章也很多,这里说下怎么使用这个"*",*的一些概念就不说了。
我在自己的mac pro上编译ios库花了将近20分钟,我把ffmpeg编译好的文件放在百度网盘上:https://pan.baidu.com/s/1xgtgku5ikxaarrqakehrsq 提取码: egt2 ,方便大家直接下载使用。
对一些静态库做下说明
libavformat:用于各种音视频封装格式的生成和解析。
libavutil:核心工具库,做一些基本音视频处理操作。
libavcodec:音视频各种格式的编解码。
libswscale:图像进行格式转换模块。
libavfilter:音视频滤镜库。
libpostproc:后期处理模块。
libswresample:用于音频重采样。
libadvice:用于硬件的音视频采集。
libavresample:这个是老版本下编译出来,新版本用libswresample代替
在ios上使用ffmpeg库进行视频相关操作.
一般用的是命令行方式,命令行涉及参数很多,编码前参考官方文档和网上文章,在自己的mac电脑上开启终端命令,建议手动测试一遍对应的命令。
第一步, 先安装ffmpeg
brew install ffmpeg
第二步,准备好视频相关文件。
第三步, 测试开发场景命令行
格式说明: -i filename 输入文件 -t duration 设置处理时间 -ss position 设置开始时间 -b:v bitrate 设置视频码率 -b:a bitrate 设置音频码率 -r fps 设置帧率 -s wxh 设置帧大小,格式为wxh,例如640x480 -c:v codec 设置视频编码器 -c:a codec 设置音频编码器 -ar freq 设置音频采样率 一些示例: -codec copy 强制使用codec编解码方式,copy表示不进行重新编码 -vcodec copy -acodec copy 跟上面一样 -vn 取消视频输出 -an 取消音频输出 -bsf:v h264_mp4toannexb 视频数据使用h264_mp4toannexb这个bitstream filter来转换为原始的h264数据 -f mp4 指定输出格式为mp4
分享一些示例,也欢迎大家留言一起探讨 !
ffmpeg -ss 00:00:15 -t 00:00:05 -i input.mp4 -vcodec copy -acodec copy output.mp4
视频剪切 从input.mp4的15秒开始,截取后5秒钟的视频,保存在output.mp4
ffmpeg -i input.mp4 -vn -acodec copy output.m4a
-vn 取消视频输出 -acodec 指定音频编码 copy代表不进行重新编码 提取一个视频文件中的音频文件
ffmpeg -i input.mp4 -an -vcodec copy output.mp4
-an 取消音频输出 -vcodec 指定视频编码 copy代表不进行重新编码 使一个视频中的音频静音,只保留视频
ffmpeg -i output.mp4 -an -vcodec copy -bsf:v h264_mp4toannexb output.h264
视频数据使用h264_mp4toannexb这个bitstream filter来转换为原始的h264数据。 从mp4文件中抽取视频流导出为裸h264数据
ffmpeg -i test.aac -i test.h264 -acodec copy -bsf:a aac_adtstoasc -vcodec copy -f mp4 output.mp4
-f 指定输出格式 使用aac音频数据和h264的视频生成mp4文件
ffmpeg -i input.wav -acodec libfdk_aac output.aac
对音频文件的编码格式做转换
ffmpeg -i input.wav -acodec pcm_s16le -f s16le output.pcm
从wav音频文件中导出pcm裸数据
ffmpeg -i input.flv -vcodec libx264 -acodec copy output.mp4
重新编码视频文件,复制音频流 同时封装到mp4格式文件中
ffmpeg -i input.mp4 -vf scale=100:-1 -t 5 -r 10 image.gif
-vf设置视频的过滤器 按照分辨率比例不动宽度改为100(使用videofilter的scalefilter),帧率改为10(-r)时长改为5(-t) 将一个mp4格式的视频转换为gif格式的动图
ffmpeg -i input.mp4 -r 0.25 frames_%04d.png
每4 秒截取一帧视频生成一张图片,生成的图片从frames_0001.png开始递增
ffmpeg -i frames_%04d.png -r 5 output.gif
使用一组图片可以组成一个gif
ffmpeg -i input.wav -af ‘volume=0.5’ output.wav
使用音量效果器,改变一个音频媒体文件中的音量
ffmpeg -i input.wav -filter_complex afade=t=in:ss=0:d=5 output.wav
将该音频文件前5秒钟做一个淡入效果
ffmpeg -i input.wav -filter_complex afade=t=out:st=200:d=5 output.wav
将音频从200s开始 做5秒的淡出效果
ffmpeg -i input1.wav -i input2.wav -filter_complex amix=inputs=2:duration=shortest output.wav
将两个音频进行合并,按照时间较短的音频时间长度作为输出
ffmpeg -i input.wav -filter_complex atempo=0.5 output.wav
将音频按照0.5倍的速度进行处理生成output.wav 时间长度变为原来的2倍,音高不变
ffmpeg -i test.avi -i frames_0004.jpeg -filter_complex overlay after.avi
给视频添加水印
ffmpeg -i test.avi -vf"drawtext=fontfile=simhei.ttf:text=‘雷’:x=100:y=10:fontsize=24:fontcolor=yellow:shadowy=2" after.avi
添加文字水印
ffmpeg -i input.flv -c:v libx264 -b:v 800k -c:a libfdk_aac -vf eq=brightness=0.25 -f mp4 output.mp4
视频提高亮度 参数brightness 取值范围-1.0到1.0。
ffmpeg -i input.flv -c:v libx264 -b:v 800k -c:a libfdk_aac -vf eq=contrast=1.5 -f mp4 output.mp4
视频增加对比度 参数contrast,取值范围是从-2.0到2.0
ffmpeg -i input.mp4 -vf “transpose=1” -b:v 600k output.mp4
视频旋转效果器使用
ffmpeg -i input.mp4 -an -vf “crop=240:480:120:0” -vcodec libx264 -b:v 600k output.mp4
视频裁剪效果器使用
ffmpeg -f rawvideo -pix_fmt rgba -s 480*480 -i texture.rgb -f image2 -vcodec mjpeg output.jpg
将一张rgba格式表示的数据转换为jpeg格式的图片
ffmpeg -f rawvideo -pix_fmt yuv420p -s 480*480 -i texture.yuv -f image2 -vcodec mjpeg output.jpg
将一个yuv格式表示的数据转换为jpeg格式的图片
ffmpeg -re -i ipnut.mp4 -acodec copy -vcodec copy -f flv rtmp://xxx
将一段视频推送到流媒体服务器上
ffmpeg -i http://xxx/xxx.flv -acodec copy -vcodec copy -f flv output.flv
将流媒体服务器上的流dump到本地
额外分享ffprobe和ffplay的一些命令辅助测试.
ffprobe常用命令 ffprobe 文件名 查看音频视频文件信息 ffprobe -show_format 文件名 查看文件的输出格式信息,时间长度,文件大小,比特率,流数目等。 ffprobe -print_format json -show_streams 文件名 以json格式输出详细信息 ffprobe -show_frames 文件名 显示帧信息 ffprobe -show_packets 文件名 查看包信息 ffplay常用命令 ffplay 文件名 播放音频、视频文件 ffplay 文件名 -loop 10 循环播放文件10次 ffplay 文件名 -ast 1 播放视频第一路音频流 参数如果是2 就是第二路音频流 如果没有就会静音。 ffplay 文件名 -vst 1 播放视频第一路视频流 参数如果是2 就是第二路视频流 没有显示黑屏 ffplay .pcm文件 -f 格式 -channels 2 声道数 -ar 采样率 播放pcm文件 必须设置参数正确 ffplay -f rawvideo -pixel_format yuv420p -s 480480 .yuv文件 -f rawvideo 代表原始格式 -pixel_format yuv420p 表示格式 -s 480480 宽高 ffplay -f rawvideo -pixel_format rgb24 -s 480*480 .rgb文件 播放rgb原始数据 ffplay 文件名 -sync audio 指定ffplay使用音频为基准进行音视频同步默认ffplay也是这样 ffplay 文件名 -sync video 指定ffplay使用视频为基准进行音视频同步 ffplay 文件名 -ext video 指定ffplay使用外部时钟为基准进行音视频同步
ffmpeg官方调用文档: ,可查看相关参数使用
上面是用命令行方式的调用,需要额外自己编译ffmpeg对应版本的命令行静态库,才能在ios里方便调用,除此以外还可以使用api方式的代码调用
在官网上
可以找到对应版本的api接口文档。
再分享一个ffmpeg3.0版本的测试demo 链接: https://pan.baidu.com/s/1rzpipny79_ze-gxsqqpryw 提取码: jfd2 大家可以直接下载使用,测试学习相关ffmpeg命令!
6.推流
可用这个链接: 测试下视频推流。
上一篇: 记一次Node项目的优化
下一篇: HTTP请求的缓存(Cache)机制