HTTP Live Streaming直播(iOS直播)技术分析与实现

不经意间发现,大半年没写博客了,自觉汗颜。实则2012后半年,家中的事一样接着一样发生,实在是没有时间。快过年了,总算忙里偷闲,把最近的一些技术成果,总结成了文章,与大家分享。

  前些日子,也是项目需要,花了一些时间研究了HTTP Live StreamingHLS)技术,并实现了一个HLS编码器HLSLiveEncoder,当然,C++写的。其功能是采集摄像头与麦克风,实时进行H.264视频编码和AAC音频编码,并按照HLS的协议规范,生成分段的标准TS文件以及m3u8索引文件。通过我的HLSLiveEncoder和第三方Http服务器(例如:Nginx),成功实现了HTTP Live Streaming直播,并在iphone上测试通过。我就把这当中的一些收获写在这里。

HLS技术要点分析

  HTTP Live StreamingHLS)是苹果公司(Apple Inc.)实现的基于HTTP的流媒体传输协议,可实现流媒体的直播和点播,主要应用在iOS系统,为iOS设备(如iPhone、iPad)提供音视频直播和点播方案。HLS点播,基本上就是常见的分段HTTP点播,不同在于,它的分段非常小。要实现HLS点播,重点在于对媒体文件分段,目前有不少开源工具可以使用,这里我就不再讨论,只谈HLS直播技术。

  相对于常见的流媒体直播协议,例如RTMP协议、RTSP协议、MMS协议等,HLS直播最大的不同在于,直播客户端获取到的,并不是一个完整的数据流。HLS协议在服务器端将直播数据流存储为连续的、很短时长的媒体文件(MPEG-TS格式),而客户端则不断的下载并播放这些小文件,因为服务器端总是会将最新的直播数据生成新的小文件,这样客户端只要不停的按顺序播放从服务器获取到的文件,就实现了直播。由此可见,基本上可以认为,HLS是以点播的技术方式来实现直播。由于数据通过HTTP协议传输,所以完全不用考虑防火墙或者代理的问题,而且分段文件的时长很短,客户端可以很快的选择和切换码率,以适应不同带宽条件下的播放。不过HLS的这种技术特点,决定了它的延迟一般总是会高于普通的流媒体直播协议。

  根据以上的了解要实现HTTP Live Streaming直播,需要研究并实现以下技术关键点

  1. 采集视频源和音频源的数据
  2. 对原始数据进行H264编码和AAC编码
  3. 视频和音频数据封装为MPEG-TS包
  4. HLS分段生成策略及m3u8索引文件
  5. HTTP传输协议

  其中第1点和第2点,我之前的文章中已经提到过了,而最后一点,我们可以借助现有的HTTP服务器,所以,实现第3点和第4点是关键所在。

程序框架与实现          

  通过以上分析,实现HLS LiveEncoder直播编码器,其逻辑和流程基本上很清楚了:分别开启音频与视频编码线程,通过DirectShow(或其他)技术来实现音视频采集,随后分别调用libx264和libfaac进行视频和音频编码。两个编码线程实时编码音视频数据后,根据自定义的分片策略,存储在某个MPEG-TS格式分段文件中,当完成一个分段文件的存储后,更新m3u8索引文件。如下图所示:

  

  上图中HLSLiveEncoder当收到视频和音频数据后,需要首先判断,当前分片是否应该结束,并创建新分片,以延续TS分片的不断生成。需要注意的是,新的分片,应当从关键帧开始,防止播放器解码失败。核心代码如下所示:

  

  TsMuxer的接口也是比较简单的。

    

HLS分段生成策略和m3u8   


1. 分段策略

  • HLS的分段策略,基本上推荐是10秒一个分片,当然,具体时间还要根据分好后的分片的实际时长做标注
  • 通常来说,为了缓存等方面的原因,在索引文件中会保留最新的三个分片地址,以类似“滑动窗口”的形式,进行更新。

2. m3u8文件简介

  m3u8,是HTTP Live Streaming直播的索引文件。m3u8基本上可以认为就是.m3u格式文件,区别在于,m3u8文件使用UTF-8字符编码。

#EXTM3U                     m3u文件头,必须放在第一行
#EXT-X-MEDIA-SEQUENCE       第一个TS分片的序列号
#EXT-X-TARGETDURATION       每个分片TS的最大的时长
#EXT-X-ALLOW-CACHE          是否允许cache
#EXT-X-ENDLIST              m3u8文件结束符
#EXTINF                     extra info,分片TS的信息,如时长,带宽等

  一个简单的m3u8索引文件

    

运行效果          

  在Nginx工作目录下启动HLSLiveEncoder,并用VLC播放器连接播放

     

  通过iPhone播放的效果

     

技术标准

 

移动视频推送标准(Mobile)

 


电脑视频推送标准(PC)

 

机顶盒视频推送标准(IPTV STB)

 

 

苹果官方对于视频直播服务提出了 HLS (HTTP Live Streaming) 解决方案,该方案主要适用范围在于:

使用 iPhone 、iPod touch、 iPad 以及 Apple TV 进行流媒体直播功能。(MAC 也能用)不使用特殊的服务软件进行流媒体直播。需要通过加密和鉴定(authentication)的视频点播服务。

首先,需要大家先对 HLS 的概念进行预览。

HLS 的目的在于,让用户可以在苹果设备(包括MAC OS X)上通过普通的网络服务完成流媒体的播放。 HLS 同时支持流媒体的实时广播点播服务。同时也支持不同 bit 速率的多个备用流(平时根据当前网速去自适应视频的清晰度),这样客户端也好根据当前网络的带宽去只能调整当前使用的视频流。安全方面,HLS 提供了通过 HTTPS 加密对媒体文件进行加密并 对用户进行验证,允许视频发布者去保护自己的网络。

 

HLS 是苹果公司QuickTime X和iPhone软件系统的一部分。它的工作原理是把整个流分成一个个小的基于HTTP的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。

在开始一个流媒体会话时,客户端会下载一个包含元数据的extended M3U (m3u8) playlist文件,用于寻找可用的媒体流。

 

HLS只请求基本的HTTP报文,与实时传输协议(RTP)不同,HLS可以穿过任何允许HTTP数据通过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。

苹果对于自家的 HLS 推广也是采取了强硬措施,当你的直播内容持续十分钟
或者每五分钟内超过 5 MB 大小时,你的 APP 直播服务必须采用 HLS 架构,否则不允许上架。(详情

相关服务支持环境 (重要组成)

  • Adobe Flash Media Server:从4.5开始支持HLS、Protected HLS(PHLS)。5.0改名为Adobe Media Server
  •  
  • Flussonic Media Server:2009年1月21日,版本3.0开始支持VOD、HLS、时移等。

  • RealNetworks的Helix Universal Server :2010年4月,版本15.0开始支持iPhone, iPad和iPod的HTTP直播、点播H.264/AAC内容,最新更新在2012年11月。
  •  
  • 微软的IIS Media Services:从4.0开始支持HLS。
  •  
  • Nginx RTMP Module:支持直播模式的HLS。
  •  
  • Nimber Streamer
  •  
  • Unified Streaming Platform

  • VLC Media Player:从2.0开始支持直播和点播HLS。
  •  
  • Wowza Media Server:2009年12月9日发布2.0,开始全面支持HLS。
  •  
  • VODOBOX Live Server:始支持HLS。
  •  
  • Gstreamill是一个支持hls输出的,基于gstreamer的实时编码器。

相关客户端支持环境

iOS从3.0开始成为标准功能。
Adobe Flash Player从11.0开始支持HLS。
Google的Android自Honeycomb(3.0)开始支持HLS。
VODOBOX HLS Player (Android,iOS, Adobe Flash Player)
JW Player (Adobe Flash player)
Windows 10 的 EDGE 浏览器开始支持HLS。
\
HLS架构

其中输入视频源是由摄像机预先录制好的。

之后这些源会被编码 MPEG-4(H.264 video 和 AAC audio)格式然后用硬件打包到MPEG-2 的传输流中。

MPEG-2 传输流会被分散为小片段然后保存为一个或多个系列的 .ts 格式的媒体文件。

这个过程需要借助编码工具来完成,比如 Apple stream segmenter

 

纯音频会被编码为一些音频小片段,通常为包含 ADTS头的AAC、MP3、或者 AC-3格式。

 

ADTS全称是(Audio Data Transport Stream),是AAC的一种十分常见的传输格式。

一般的AAC解码器都需要把AAC的ES流打包成ADTS的格式,一般是在AAC ES流前添加7个字节的ADTS header


 

ES流- Elementary Streams (原始流):对视频、音频信号及其他数据进行编码压缩后 的数据流称为原始流。原始流包括访问单元,比如视频原始流的访问单元就是一副图像的编码数据。

 

 

同时上面提到的那个切片器(segmenter)也会创建一个索引文件,通常会包含这些媒体文件的一个列表,也能包含元数据。他一般都是一个.M38U 个hi的列表。列表元素会关联一个 URL 用于客户端访问。然后按序去请求这些 URL。

服务器端

服务端可以采用硬件编码和软件编码两种形式,其功能都是按照上文描述的规则对现有的媒体文件进行切片并使用索引文件进行管理。而软件切片通常会使用 Apple 公司提供的工具或者第三方的集成工具。

媒体编码

媒体编码器获取到音视频设备的实时信号,将其编码后压缩用于传输。而编码格式必须配置为客户端所支持的格式,比如 H.264 视频和HE-AAC 音频。当前,支持 用于视频的 MPEG-2 传输流和 纯音频 MPEG 基本流。编码器通过本地网络将 MPEG-2 传输流分发出去,送到流切片器那里。标准传输流和压缩传输流无法混合使用。传输流可以被打包成很多种不同的压缩格式,这里有两个表详细列举了支持的压缩格式类型。

(这两个表还在寻找中。。。。)


Audio Technologies Vedio Technologies
[重点]在编码中图,不要修改视频编码器的设置,比如视频大小或者编码解码器类型。如果避免不了,那修改动作必须发生在一个片段边界。并且需要早之后相连的片段上用EXT-X-DISCONTINUITY 进行标记。
流切片器

流切片器(通常是一个软件)会通过本地网络从上面的媒体编码器中读取数据,然后将着这些数据一组相等时间间隔的  媒体文件。虽然没一个片段都是一个单独的文件,但是他们的来源是一个连续的流,切完照样可以无缝重构回去。

切片器在切片同时会创建一个索引文件,索引文件会包含这些切片文件的引用。每当一个切片文件生成后,索引文件都会进行更新。索引用于追踪切片文件的有效性和定位切片文件的位置。切片器同时也可以对你的媒体片段进行加密并且创建一个密钥文件作为整个过程的一部分。

文件切片器(相对于上面的流切片器)

如果已近有编码后的文件(而不是编码流),你可以使用文件切片器,通过它对编码后的媒体文件进行 MPEG-2 流的封装并且将它们分割为等长度的小片段。切片器允许你使用已经存在的音视频库用于 HLS 服务。它和流切片器的功能相似,但是处理的源从流替换流为了文件。

媒体片段文件

媒体片段是由切片器生成的,基于编码后的媒体源,并且是由一系列的 .ts 格式的文件组成,其中包含了你想通过 MPEG-2 传送流携带的 H.264 视频 和 AAC
/MP3/AC-3 音频。对于纯音频的广播,切片器可以生产 MPEG 基础音频流,其中包含了 ADTS头的AAC、MP3、或者AC3等音频。

索引文件(PlayLists)

通常由切片器附带生成,保存为 .M3U8 格式,.m3u 一般用于 MP3 音频的索引文件。
Note如果你的扩展名是.m3u,并且系统支持.mp3文件,那客户的软件可能要与典型的 MP3 playList 保持一致来完成 流网络音频的播放。

下面是一个 .M3U8 的 playlist 文件样例,其中包含了三个没有加密的十秒钟的媒体文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<code class = "cpp" >#EXT-X-VERSION: 3
#EXTM3U
#EXT-X-TARGETDURATION: 10
#EXT-X-MEDIA-SEQUENCE: 1
 
# Old-style integer duration; avoid for newer clients.
#EXTINF: 10 ,
http: //media.example.com/segment0.ts
 
# New-style floating-point duration; use for modern clients.
#EXTINF: 10.0 ,
http: //media.example.com/segment1.ts
#EXTINF: 9.5 ,
http: //media.example.com/segment2.ts
#EXT-X-ENDLIST</code>

为了更精确,你可以在 version 3 或者之后的协议版本中使用 float 数来标记媒体片段的时长,并且要明确写明版本号,如果没有版本号,则必须与 version 1 协议保持一致。你可以使用官方提供的切片器去生产各种各样的 playlist 索引文件,详见媒体文件切片器

分布式部分

分布式系统是一个网络服务或者一个网络缓存系统,用于通过 HTTP 向客户端发送媒体文件和索引文件。不用自定义模块发送内容。通常仅仅需要很简单的网络配置即可使用。而且这种配置一般就是限制指定 .M38U 文件和 .ts 文件的 MIME 类型。详见部署 HTTP Live Streaming

客户端部分

客户端开始时回去抓取 索引文件(.m3u8/.m3u),其中用URL来标记不同的流。索引文件可以指定可用媒体文件的位置,解密的密钥,以及任何可以切换的流。对于选中的流,客户端会有序的下载每一个可获得的文件。每一个文件都包含流中的连环碎片。一旦下载到足够量的数据,客户端会开始向用户展示重新装配好的媒体资源。

客户端负责抓取任何解密密钥,认证或者展示一个用于认证的界面,之后再解密需要的文件。

这个过程会一直持续知道出现 结束标记 #EXT-X-ENDLIST。如果结束标记不出现,该索引就是用于持续广播的。客户端会定期的加载一些新的索引文件。客户端会从新更新的索引文件中去查找加密密钥并且将关联的URL加入到请求队列中去。

HLS 的使用

使用 HLS 需要使用一些工具,当然大部分工具都是服务器端使用的,这里简单了解一下就行,包括 media stream segmenter, a media file segmenter, a stream validator, an id3 tag generator, a variant playlist generator.这些工具用英文注明是为了当你在苹果开发中心中寻找时方便一些。

会话模式

通常包含 Live 和 VOD (点播)两种

点播VOD的特点就是可以获取到一个静态的索引文件,其中那个包含一套完整的资源文件地址。这种模式允许客户端访问全部节目。VOD点播拥有先进的下载技术,包括加密认证技术和动态切换文件传输速率的功能(通常用于不同分辨率视频之间的切换)。

Live 会话就是实时事件的录制展示。它的索引文件一直处于动态变化的,你需要不断的更新索引文件 playlist 然后移除旧的索引文件。这种类型通过向索引文件添加媒体地址可以很容易的转化为VOD类型。在转化时不要移除原来旧的源,而是通过添加一个#ET-X-ENDLIST 标记来终止实时事件。转化时如果你的索引文件中包含 EXT-X-PLAYLIST-TYPE 标签,你需要将值从EVENT 改为 VOD

ps:自己抓了一个直播的源,从索引中看到的结果是第一次回抓到代表不同带宽的playList(抓取地址:http://dlhls.cdn.zhanqi.tv/zqlive/34338_PVMT5.m3u8)

?
1
2
3
4
5
6
7
8
<code class = "cpp" >#EXTM3U
#EXT-X-VERSION: 3
#EXT-X-STREAM-INF:PROGRAM-ID= 1 ,PUBLISHEDTIME= 1453914627 ,CURRENTTIME= 1454056509 ,BANDWIDTH= 700000 ,RESOLUTION=1280x720
34338_PVMT5_700/index.m3u8?Dnion_vsnae=34338_PVMT5
#EXT-X-STREAM-INF:PROGRAM-ID= 1 ,PUBLISHEDTIME= 1453914627 ,CURRENTTIME= 1454056535 ,BANDWIDTH= 400000
34338_PVMT5_400/index.m3u8?Dnion_vsnae=34338_PVMT5
#EXT-X-STREAM-INF:PROGRAM-ID= 1 ,PUBLISHEDTIME= 1453914627 ,CURRENTTIME= 1454056535 ,BANDWIDTH= 1024000
34338_PVMT5_1024/index.m3u8?Dnion_vsnae=34338_PVMT5</code>

这里面的链接不是视频源URL,而是一个用于流切换的主索(下面会有介绍)引我猜想是需要对上一次的抓包地址做一个拼接

组合的结果就是:http://dlhls.cdn.zhanqi.tv/zqlive/34338_PVMT5_1024/index.m3u8?Dnion_vsnae=34338_PVMT5(纯属小学智力题。。。)将它作为抓取地址再一次的结果

?
1
2
3
4
5
6
7
8
9
10
<code class = "cpp" >#EXTM3U
#EXT-X-VERSION: 3
#EXT-X-MEDIA-SEQUENCE: 134611
#EXT-X-TARGETDURATION: 10
#EXTINF: 9.960 ,
35 /1454056634183_128883.ts?Dnion_vsnae=34338_PVMT5
#EXTINF: 9.960 ,
35 /1454056644149_128892.ts?Dnion_vsnae=34338_PVMT5
#EXTINF: 9.960 ,
35 /1454056654075_128901.ts?Dnion_vsnae=34338_PVMT5</code>

同理,继续向下抓:(拼接地址:http://dlhls.cdn.zhanqi.tv/zqlive/34338_PVMT5_1024/index.m3u8?Dnion_vsnae=34338_PVMT5/35/1454056634183_128883.ts?Dnion_vsnae=34338_PVMT5/36/1454059958599_131904.ts?Dnion_vsnae=34338_PVMT5
抓取结果:

?
1
2
3
4
5
6
7
8
9
10
<code class = "cpp" >#EXTM3U
#EXT-X-VERSION: 3
#EXT-X-MEDIA-SEQUENCE: 134984
#EXT-X-TARGETDURATION: 10
#EXTINF: 9.280 ,
36 /1454059988579_131931.ts?Dnion_vsnae=34338_PVMT5
#EXTINF: 9.960 ,
36 /1454059998012_131940.ts?Dnion_vsnae=34338_PVMT5
#EXTINF: 9.960 ,
36 /1454060007871_131949.ts?Dnion_vsnae=34338_PVMT5</code>

相比于第二次又获取了一个片段的索引,而且只要是第二次之后,资源地址都会包含 .ts,说明里面是有视频资源URL的,不过具体的截取方法还是需要查看前面提到的IETF的那套标准的HLS的协议,利用里面的协议应该就能拼接出完整的资源路径进行下载。反正我用苹果自带的MPMoviePlayerController直接播放是没有问题的,的确是直播资源。与之前说过的苹果自带的QuickTime类似,都遵循了HLS协议用于流媒体播放。而每次通过拼接获取下一次的索引,符合协议里提到的不断的更替索引的动作。

内容加密

如果内容需要加密,你可以在索引文件中找到密钥的相关信息。如果索引文件中包含了一个密钥文件的信息,那接下来的媒体文件就必须使用密钥解密后才能解密打开了。当前的 HLS 支持使用16-octet 类型密钥的 AES-128 加密。这个密钥格式是一个由着在二进制格式中的16个八进制组的数组打包而成的。

加密的配置模式通常包含三种:

模式一:允许你在磁盘上制定一个密钥文件路径,切片器会在索引文件中插入存在的密钥文件的 URL。所有的媒体文件都使用该密钥进行加密。模式二:切片器会生成一个随机密钥文件,将它保存在指定的路径,并在索引文件中引用它。所有的媒体文件都会使用这个随机密钥进行加密。模式三:每 n 个片段生成一个随机密钥文件,并保存到指定的位置,在索引中引用它。这个模式的密钥处于轮流加密状态。每一组 n 个片段文件会使用不同的密钥加密。

理论上,不定期的碎片个数生成密钥会更安全,但是定期的生成密钥不会对系统的性能产生太大的影响。

你可以通过 HTTP 或者 HTTPS 提供密钥。也可以选择使用你自己的基于会话的认证安排去保护发送的key。更多详情可以参考通过 HTTPS 安全的提供预约

密钥文件需要一个 initialization vector (IV) 去解码加密的媒体文件。IV 可以随着密钥定期的改变。

缓存和发送协议

HTTPS通常用于发送密钥,同时,他也可以用于平时的媒体片段和索引文件的传输。但是当扩展性更重要时,这样做是不推荐的。HTTPS 请求通常都是绕开 web 服务缓存,导致所有内容请求都是通过你的服务进行转发,这有悖于分布式网络连接系统的目的。

处于这个原因,确保你发送的网络内容都明白非常重要。当处于实况广播模式时索引文件不会像分片媒体文件一样长时间的被缓存,他会动态不停地变化。

流切换

如果你的视频具备流切换功能,这对于用户来说是一个非常棒的体验,处于不同的带宽、不同的网速播放不同清晰度的视频流,这样只能的流切换可以保证用户感觉到非常流畅的观影体验,同时不同的设备也可以作为选择的条件,比如视网膜屏可以再网速良好的情况下播放清晰度更高的视频流。

这种功能的实现在于,索引文件的特殊结构

\
流切换索引文件结构

有别于普通的索引,具备流热切换的索引通常由主索引和链接不同带宽速率的资源的子索引,由子索引再链接对引得.ts视频切片文件。其中主索引只下载一次,而子索引则会不停定期的下载,通常会先使用主索引中列出的第一个子索引,之后才会根据当时的网络情况去动态切换合适的流。客户端会在任何时间去切换不同的流。比如连入或者退出一个 wifi 热点。所有的切换都会使用相同的音频文件(换音频没多大意思相对于视频)在不同的流之间平滑的进行切换。
这一套不同速率的视频都是有工具生成的,使用variantplaylistcreator 工具并且为 mediafilesegmenter 或者mediastreamsegmenter 指定 -generate-variant-playlist 选项,详情参考 下载工具

概念先写到这吧,前面的知识够对HSL的整体结构做一个初步的了解。

 

 

 

Demo配置原理:

1、 需要导入第三方库:ASIHttpRequest,CocoaHTTPServer,m3u8(其中ASI用于网络请求,CocoaHTTPServer用于在ios端搭建服务器使用,m3u8是用来对返回的索引文件进行解析的)

\
ASI配置注意事项  \
MRC报错处理

2、导入系统库:libsqlite3.dylib、libz.dylib、libxml2.dylib、CoreTelephony.framework、SystemConfiguration.framework、MobileCoreServices.framework、Security.framework、CFNetwork.framework、MediaPlayer.framework

3、添加头文件

?
1
<code class = "css" >YCHLS-Demo.h</code>

4、demo介绍

\
demo样式  播放: 直接播放在线的直播链接,是由系统的MPMoviePlayer完成的,它自带解析HLS直播链的功能。 下载: 遵循HLS的协议,通过索引文件的资源路径下载相关的视频切片并保存到手机本地。 播放本地视频: 使用下载好的视频文件片段进行连续播放。 清除缓存: 删除下载好的视频片段

原理:

通过ASI请求链接,通过m3u8库解析返回的m3u8索引文件。再通过ASI下载解析出的视频资源地址,仿照HLS中文件存储路径存储。利用CocoaHTTPServer在iOS端搭建本地服务器,并开启服务,端口号为:12345(高位端口即可)。配置服务器路径与步骤二存储路径一致。设置播放器直播链接为本地服务器地址,直接播放,由于播放器遵守HLS协议,所以能够解析我们之前使用HLS协议搭建的本地服务器地址。点击在线播放,校验是否与本地播放效果一致。  \
HLS协议文件存储结构

上面是HLS中服务器存储视频文件切片和索引文件的结构图
整个流程就是:

先点击下载,通过解析m3u8的第三方库解析资源。(m3u8的那个库只能解析一种特定格式的m3u8文件,代码里会有标注)点击播放本地视频播放下载好的资源。点击播放是用来预览直播的效果,与整个流程无关。其中进度条用来显示下载进度。

总结:
整个Demo并不只是让我们搭建一个Hls服务器或者一个支持Hls的播放器。目的在于了解Hls协议的具体实现,以及服务器端的一些物理架构。通过Demo的学习,可以详细的了解Hls直播具体的实现流程。

部分源码贴出

开启本地服务器:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<code class = "objectivec" >- ( void )openHttpServer
{
     self.httpServer = [[HTTPServer alloc] init];
     [self.httpServer setType:@ "_http._tcp." ];  // 设置服务类型
     [self.httpServer setPort: 12345 ]; // 设置服务器端口
 
     // 获取本地Library/Cache路径下downloads路径
     NSString *webPath = [kLibraryCache stringByAppendingPathComponent:kPathDownload];
     NSLog(@ "-------------\\nSetting document root: %@\\n" , webPath);
     // 设置服务器路径
     [self.httpServer setDocumentRoot:webPath];
     NSError *error;
     if (![self.httpServer start:&error])
     {
         NSLog(@ "-------------\\nError starting HTTP Server: %@\\n" , error);
     }</code>

视频下载:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<code class = "objectivec" >- (IBAction)downloadStreamingMedia:(id)sender {
 
     UIButton *downloadButton = sender;
     // 获取本地Library/Cache路径
     NSString *localDownloadsPath = [kLibraryCache stringByAppendingPathComponent:kPathDownload];
 
     // 获取视频本地路径
     NSString *filePath = [localDownloadsPath stringByAppendingPathComponent:@ "XNjUxMTE4NDAw/movie.m3u8" ];
     NSFileManager *fileManager = [NSFileManager defaultManager];
     // 判断视频是否缓存完成,如果完成则播放本地缓存
     if ([fileManager fileExistsAtPath:filePath]) {
         [downloadButton setTitle:@ "已完成" forState:UIControlStateNormal];
         downloadButton.enabled = NO;
     } else {
         M3U8Handler *handler = [[M3U8Handler alloc] init];
         handler.delegate = self;
         // 解析m3u8视频地址
         [handler praseUrl:TEST_HLS_URL];
         // 开启网络指示器
         [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
     }
}</code>

播放本地视频:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<code class = "objectivec" >- (IBAction)playVideoFromLocal:(id)sender {
 
     NSString * playurl = [NSString stringWithFormat:@ "http://127.0.0.1:12345/XNjUxMTE4NDAw/movie.m3u8" ];
     NSLog(@ "本地视频地址-----%@" , playurl);
 
     // 获取本地Library/Cache路径
     NSString *localDownloadsPath = [kLibraryCache stringByAppendingPathComponent:kPathDownload];
     // 获取视频本地路径
     NSString *filePath = [localDownloadsPath stringByAppendingPathComponent:@ "XNjUxMTE4NDAw/movie.m3u8" ];
     NSFileManager *fileManager = [NSFileManager defaultManager];
 
     // 判断视频是否缓存完成,如果完成则播放本地缓存
     if ([fileManager fileExistsAtPath:filePath]) {
         MPMoviePlayerViewController *playerViewController =[[MPMoviePlayerViewController alloc]initWithContentURL:[NSURL URLWithString: playurl]];
         [self presentMoviePlayerViewControllerAnimated:playerViewController];
     }
     else {
         UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@ "Sorry" message:@ "当前视频未缓存" delegate:self cancelButtonTitle:@ "确定" otherButtonTitles:nil, nil];
         [alertView show];
     }
}</code>

播放在线视频

?
1
2
3
4
5
6
<code class = "objectivec" >- (IBAction)playLiveStreaming {
 
     NSURL *url = [[NSURL alloc] initWithString:TEST_HLS_URL];
     MPMoviePlayerViewController *player = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
     [self presentMoviePlayerViewControllerAnimated:player];
}</code>

 

ADTS是个啥

ADTS全称是(Audio Data Transport Stream),是AAC的一种十分常见的传输格式。

一般的AAC解码器都需要把AAC的ES流打包成ADTS的格式,一般是在AAC ES流前添加7个字节的ADTS header

也就是说你可以吧ADTS这个头看作是AAC的frameheader。

 

ADTS AAC
ADTS_headerAAC ESADTS_headerAAC ES
...
ADTS_headerAAC ES

2.ADTS内容及结构

ADTS 头中相对有用的信息采样率、声道数、帧长度

想想也是,我要是解码器的话,你给我一堆得AAC音频 ES流我也解不出来。

每一个带ADTS头信息的AAC流会清晰的告送解码器他需要的这些信息。

一般情况下ADTS的头信息都是7个字节,7*8 = 56 个bit,分为2部分:

固定头 adts_fixed_header(); 28bit

可变头 adts_variable_header(); 28bit

\

其中:

syncword:同步头 总是0xFFF,12 bit, all bits must be 1,代表着一个ADTS帧的开始

ID:MPEG Version: 0 for MPEG-4, 1 for MPEG-2

Layer:always: '00'

profile:表示使用哪个级别的AAC,有些芯片只支持AAC LC 。

在MPEG-2 AAC中定义了3种:

\

sampling_frequency_index:

表示使用的采样率下标,通过这个下标在Sampling Frequencies[ ]数组中查找得知采样率的值。

There are 13 supported frequencies:

  • 0: 96000 Hz
  • 1: 88200 Hz
  • 2: 64000 Hz
  • 3: 48000 Hz
  • 4: 44100 Hz
  • 5: 32000 Hz
  • 6: 24000 Hz
  • 7: 22050 Hz
  • 8: 16000 Hz
  • 9: 12000 Hz
  • 10: 11025 Hz
  • 11: 8000 Hz
  • 12: 7350 Hz
  • 13: Reserved
  • 14: Reserved
  • 15: frequency is written explictly channel_configuration:表示声道数
    • 0: Defined in AOT Specifc Config
    • 1: 1 channel: front-center
    • 2: 2 channels: front-left, front-right
    • 3: 3 channels: front-center, front-left, front-right
    • 4: 4 channels: front-center, front-left, front-right, back-center
    • 5: 5 channels: front-center, front-left, front-right, back-left, back-right
    • 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
    • 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
    • 8-15: Reserved
         可变头 28bit
\
 

frame_length:

一个ADTS帧的长度包括ADTS头和AAC原始流.

 

adts_buffer_fullness:0x7FF 说明是码率可变的码流

 

3.将AAC打包成ADTS格式

如果是通过嵌入式高清解码芯片做产品的话,一般情况的解码工作都是由硬件来完成的。所以大部分的工作是把AAC原始流打包成ADTS的格式,然后丢给硬件就行了。

通过对ADTS格式的了解,很容易就能把AAC打包成ADTS。我们只需得到封装格式里面关于音频采样率、声道数、元数据长度、aac格式类型等信息。然后在每个AAC原始流前面加上个ADTS头就OK了。

贴上ffmpeg中添加ADTS头的代码,就可以很清晰的了解ADTS头的结构:

[html] view plain copy
  1. intff_adts_write_frame_header(ADTSContext*ctx,
  2. uint8_t*buf,intsize,intpce_size)
  3. {
  4. PutBitContextpb;
  5.  
  6. init_put_bits(&pb,buf,ADTS_HEADER_SIZE);
  7.  
  8. /*adts_fixed_header*/
  9. put_bits(&pb,12,0xfff);/*syncword固定的 */
  10. put_bits(&pb,1,0);/*ID*/
  11. put_bits(&pb,2,0);/*layer*/
  12. put_bits(&pb,1,1);/*protection_absent*/
  13. put_bits(&pb,2,ctx->objecttype);/*profile_objecttype*/
  14. put_bits(&pb,4,ctx->sample_rate_index);
  15. put_bits(&pb,1,0);/*private_bit*/
  16. put_bits(&pb,3,ctx->channel_conf);/*channel_configuration*/
  17. put_bits(&pb,1,0);/*original_copy*/
  18. put_bits(&pb,1,0);/*home*/
  19.  
  20. /*adts_variable_header*/
  21. put_bits(&pb,1,0);/*copyright_identification_bit*/
  22. put_bits(&pb,1,0);/*copyright_identification_start*/
  23. put_bits(&pb,13,ADTS_HEADER_SIZE+size+pce_size);/*aac_frame_length*/
  24. put_bits(&pb,11,0x7ff);/*adts_buffer_fullness*/
  25. put_bits(&pb,2,0);/*number_of_raw_data_blocks_in_frame*/
  26.  
  27. flush_put_bits(&pb);
  28.  
  29. return0;
  30. }


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值