从 0 到 1 打造直播 App

kejt4875 7年前
   <h2><strong>概要</strong></h2>    <p><strong>分享内容</strong>:</p>    <p>互联网内容载体变迁历程,文字——图片/声音——视频——VR/AR——…….。从直播1.0秀场时代(YY),2.0游戏直播(斗鱼、虎牙、熊猫)到如今全民直播3.0泛生活娱乐时代(映客、花椒),国外直播app(Meerkat 、Periscope),随着VA/AR/MR提出的沉浸式视听体验,直播4.0时代很快就能到来。</p>    <p>在这个全民娱乐的时代,直播已经火得不要不要的,各大公司都有自己的直播产品。本文主要从直播的一些基本知识,一步步打造直播app。直播那么火的背后有什么样的技术支撑呢?</p>    <p>先将这些APP按照视频网站按照视频网站、弹幕视频、直播平台、在线秀场、移动短视频、移动直播来划分类别。再按照内容和社交这个维度来进行区分,可以明显看出视频网站、弹幕网站和直播平台更偏内容,他们对内容的需求更加高,用户在上面进行社交沉淀相对比较浅。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/837b6548c48608d511dcccf803d86411.jpg"></p>    <p>而后面三者更加偏向社交,他们强调人而不强调内容。所以短期内不会有大的竞争关系,只是前三类、后三者之间的竞争会出现。</p>    <p><strong>大体框架</strong></p>    <p>基本是下图这个套路:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/21c00b3e0f5fe713ec780b72e861c5f5.png"></p>    <p>录制->编码->网络传输->解码->播放</p>    <p>以上为直播的整体流程,根据该流程分为以下技术点:</p>    <ol>     <li> <p>怎样录制直播视频</p> </li>     <li> <p>怎样实时上传直播视频</p> </li>     <li> <p>怎样播放直播视频</p> </li>     <li> <p>直播间的用户是如何交互</p> </li>    </ol>    <h2><strong>一、移动视频直播发展</strong></h2>    <p>PC直播(固定场所)——>移动端(形式自由)。</p>    <p>随着越来越多的直播类 App 上线,移动直播进入了前所未有的爆发阶段,目前大多数移动直播以 Native 客户端为主。但是H5端的直播在移动直播端也承载着不可替代的作用,例如 H5 有着传播快,易发布的优势。</p>    <p>完整的直播包括:</p>    <ol>     <li> <p>视频录制端</p> <p>电脑上的音视频输入设备或者手机端的摄像头或者麦克风,目前以移动端的手机视频为主。</p> </li>     <li> <p>视频播放端</p> <p>可以是电脑上的播放器,手机端的 Native 播放器,还有 H5 的 video 标签等。</p> </li>     <li> <p>流媒体服务器端</p> <p>用来接受视频录制端提供的视频源,同时提供给视频播放端流服务。目前开源的流媒体有RED5,CRTMPD,NGINX-RTMP,SRS。</p> </li>    </ol>    <h2><strong>二、录制视频</strong></h2>    <p>如何生产视频数据</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/bba53d51f41e974b7ef2c7700e8cc867.jpg"></p>    <p>封装格式的主要作用是把视频码流和音频码流按照一定的格式存储在一个文件中。</p>    <p>为什么要分封装格式和视频编码格式呢?</p>    <p>这个其实跟网络分七层模型一个原理。解耦和,降低依赖,底层给上层提供基础功能,底层和上层都都可以单独扩展,可以以多种方案组合编码与封装,比如MP4与H264、MP4与MPEG、TS与H264等等。比如这里面的这边文章的编码就只负责将最原始的音频和视频数据就行压缩,而压缩完的数据要怎么组织就拜托给上层的封装,封装接到视频音频数据负责给数据编号,指定同步协议,加入字幕等操作。经过封装后,得到的就是可以播放的上面提到的视频文件MP4或者MKV等等。把这个过程反过来就是上图描述的视频播放的过程。</p>    <h3><strong>1、流媒体源</strong></h3>    <ol>     <li> <p>PC端的摄像头、屏幕</p> <p>对于PC端的流媒体源,可以使用Open Broadcaster Software串流(支持多种直播平台)。</p> </li>     <li> <p>移动端iOS、Android的摄像头和麦克风。</p> <p>iOS、Android主要是系统提供的API实现。</p> </li>     <li> <p>webRTC (Web Real-Time Communication)</p> <p>webRTC是一个支持网页浏览器进行实时语音对话或视频对话的技术,可以在网页浏览器中进行采集、传输、播放,缺点是只在 PC 的 Chrome 上支持较好,移动端支持不太理想。</p> </li>    </ol>    <p>使用 webRTC 录制视频基本流程是:</p>    <pre>  window.navigator.webkitGetUserMedia()  window.webkitRTCPeerConnection  webscoket  </pre>    <p>由于许多方法都要加上浏览器前缀,所以很多移动端的浏览器还不支持 webRTC,所以真正的视频录制还是要靠客户端(iOS,Android)来实现,效果会好一些。</p>    <h3><strong>2、编码</strong></h3>    <p>推荐Andorid4.3(API18)或以上使用硬编,以下版本使用软编;iOS使用全硬编方案。</p>    <ol>     <li> <p>软编码:</p> <p>libffmpeg</p> </li>     <li> <p>硬编码:</p> <p>MediaCodec(sdk level 16+, Android 4.1, 4.1.1, the JELLY_BEAN)</p> </li>    </ol>    <h3><strong>3、封装</strong></h3>    <p>FLV(Flash Video)是Adobe公司设计开发的一种流行的流媒体格式,FLV可以使用Flash Player进行播放,FLV封装格式的文件后缀通常为“.flv”。总体上看,FLV包括文件头(File Header)和文件体(File Body)两部分,其中文件体由一系列的Tag组成。</p>    <p>特点:视频文件体积轻巧、封装简单</p>    <p>每个Tag前面还包含了Previous Tag Size字段,表示前面一个Tag的大小。Tag的类型可以是视频、音频和Script,每个Tag只能包含以上三种类型的数据中的一种。图2展示了FLV文件的详细结构。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6a1e39b330c94243dab4a3c5822e8520.jpg"></p>    <p>Tag Data</p>    <ol>     <li style="text-align: center;"> <p style="text-align: left;">Audio Tag</p> <img src="https://simg.open-open.com/show/45b0022e98c59fc7ddc9298d5af9e80d.png"></li>     <li style="text-align: center;"> <p style="text-align: left;">Video Tag</p> <img src="https://simg.open-open.com/show/3fcceda6f9dfcaa0e940117bb82d57cf.png"></li>     <li> <p>Script Tag(控制帧)或叫meta data tag</p> <p>该类型Tag又通常被称为Metadata Tag,会放一些关于FLV视频和音频的元数据信息如:duration、width、height等。通常该类型Tag会跟在File Header后面作为第一个Tag出现,而且只有一个。</p> </li>    </ol>    <p>如图以Android为例的推流的流程图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f7a20e303c4e24ab72877ccdea4d22b0.jpg"></p>    <h2><strong>三、视频推流(Stream)</strong></h2>    <p>如何推</p>    <p>往哪里推</p>    <h3><strong>1、协议</strong></h3>    <p>国内常见公开的直播协议有几个:RTMP、HDL(HTTP-FLV)、HLS、RTP。</p>    <p><strong>RTMP</strong></p>    <p>Real Time Messaging Protocol是 Macromedia 开发的一套视频直播协议,现在属于 Adobe。</p>    <p>使用RTMP技术的流媒体系统有一个非常明显的特点:使用 Flash Player 作为播放器客户端,而Flash Player 现在已经安装在了全世界将近99%的PC上,因此一般情况下收看RTMP流媒体系统的视音频是不需要安装插件的。用户只需要打开网页,就可以直接收看流媒体。</p>    <p>和 HLS 一样都可以应用于视频直播,区别是 RTMP 基于 flash 无法在 iOS 的浏览器里播放,但是实时性比 HLS 要好。所以一般使用这种协议来上传视频流,也就是视频流推送到服务器。</p>    <p>rtmp现在大部分国外的CDN已不支持,在国内流行度很高。原因有几个方面:</p>    <ol>     <li> <p>开源软件和开源库的支持稳定完整。如斗鱼主播常用的OBS软件,开源的librtmp库,服务端有nginx-rtmp插件。</p> </li>     <li> <p>播放端安装率高。只要浏览器支持FlashPlayer就能播放RTMP的直播。相对其他协议而言,RTMP协议初次建立连接的时候握手过程过于复杂(RTMP协议本身的交互是基于TCP),视不同的网络状况会带来给首开带来100ms以上的延迟。基于RTMP延迟在2~5秒。</p> </li>    </ol>    <p><strong>HTTP-FLV</strong></p>    <p>即使用HTTP协议流式的传输媒体内容,直接向后台上传编码后的流媒体数据。相对于RTMP,HTTP更简单和广为人知,而且不担心被Adobe的专利绑架。内容延迟同样可以做到2~5秒,打开速度更快,因为HTTP本身没有复杂的状态交互。所以从延迟角度来看,HTTP-FLV要优于RTMP。</p>    <p><strong>HLS</strong></p>    <p>即Http Live Streaming,是由苹果提出基于HTTP的流媒体传输协议。HLS有一个非常大的优点:HTML5可以直接打开播放;这个意味着可以把一个直播链接通过微信等转发分享,不需要安装任何独立的APP,有浏览器即可,所以流行度很高。社交直播APP,HLS可以说是刚需 。</p>    <p>Issue :SRS3.0提出了一种增强型HLS+</p>    <p><strong>RTP</strong></p>    <p>Real-time Transport Protocol,用于Internet上针对多媒体数据流的一种传输层协议。</p>    <p>实际应用场景下经常需要RTCP(RTP Control Protocol)配合来使用,可以简单理解为RTCP传输交互控制的信令,RTP传输实际的媒体数据。</p>    <p>RTP在视频监控、视频会议、IP电话上有广泛的应用,因为视频会议、IP电话的一个重要的使用体验:内容实时性强。</p>    <p>对比与上述3种或实际是2种协议,RTP和它们有一个重要的区别就是默认是使用UDP协议来传输数据,而RTMP和HTTP-FLV是基于TCP协议传输。</p>    <ul>     <li> <p>UDP:单个数据报,不用建立连接,简单,不可靠,会丢包,会乱序;</p> </li>     <li> <p>TCP:流式,需要建立连接,复杂,可靠 ,有序。</p> </li>    </ul>    <p>实时音视频流的场景不需要可靠保障,因此也不需要有重传的机制,实时的看到图像声音,网络抖动时丢了一些内容,画面模糊和花屏,完全不重要。TCP为了重传会造成延迟与不同步,如某一截内容因为重传,导致1秒以后才到,那么整个对话就延迟了1秒,随着网络抖动,延迟还会增加成2秒、3秒,如果客户端播放是不加以处理将严重影响直播的体验。</p>    <p>是否有除了HLS外更低延迟的方案?</p>    <p>HLS的优点点是显而易见的:移动端无需安装APP使用兼容HTML5的浏览器打开即可观看,所有主流的移动端浏览器基本都支持HTML5,在直播的传播和体验上有巨大的优势。</p>    <p>下面是 HTTP-FLV、HLS 、 RTMP 的对比:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c0a23c9d0d9716ea1cd6da72c736e07d.png"></p>    <h3><strong>2、推流</strong></h3>    <p>所谓推流,就是将我们已经编码好的音视频数据发往视频流服务器中,常用的第三方库 librtmp-iOS 进行推流,librtmp 封装了一些核心的 API 供使用者调用。例如推流 API 等等,配置服务器地址,即可将转码后的视频流推往服务器。一般的推流服务器都配置了服务器端信息。</p>    <p><strong>推流SDK</strong></p>    <p>百度云推流SDK: 官方文档</p>    <p>七牛推流SDK:         Github上的官方源码及说明</p>    <p>网易云推流SDK: 官方文档</p>    <p>腾讯云推流SDK: 官方文档</p>    <p>其他推流SDK:</p>    <p>https://github.com/daniulive/SmarterStreaming</p>    <p>https://github.com/leixiaohua1020/simplest_ffmpeg_mobile</p>    <p>https://github.com/begeekmyfriend/yasea</p>    <p>https://github.com/simple-rtmp-server/srs-sea</p>    <h3><strong>推流服务器</strong></h3>    <p>那么如何搭建一个推流服务器呢?</p>    <p>简单的推流服务器搭建,服务器支持 RTMP ,大概需要以下几个步骤:</p>    <ol>     <li> <p>安装一台 nginx 服务器。</p> </li>     <li> <p>安装 nginx 的 RTMP 扩展,目前使用比较多的是 https://github.com/arut/nginx-rtmp-module</p> </li>     <li> <p>配置 nginx 的 conf 文件</p> </li>     <li> <p>重启 nginx,将 RTMP 的推流地址写为 rtmp://ip:1935/hls/mystream, 其中 hls_path 表示生成的 .m3u8 和 ts 文件所存放的地址,hls_fragment 表示切片时长,mysteam 表示一个实例,即将来要生成的文件名可以先自己随便设置一个。</p> <p>更多配置可以参考:</p> <p>https://github.com/arut/nginx-rtmp-module/wiki/</p> </li>    </ol>    <p>下面是 nginx 的配置文件</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/da6600e911530f7a871851b244bf0697.png"></p>    <p><strong>腾讯云直播</strong></p>    <p>[后台SDK]主要是调用腾讯云API。</p>    <p>[服务器API]提供了直播控制台api概览:</p>    <ul>     <li> <p>创建直播频道    CreateLVBChannel</p> </li>     <li> <p>查询直播频道列表    DescribeLVBChannelList</p> </li>     <li> <p>查询直播频道详情    DescribeLVBChannel</p> </li>     <li> <p>修改直播频道    ModifyLVBChannel</p> </li>     <li> <p>批量启用直播频道    StartLVBChannel</p> </li>     <li> <p>批量停止直播频道    StopLVBChannel</p> </li>     <li> <p>查询直播频道当前并发收看数    DescribeLVBOnlineUsers</p> </li>     <li> <p>删除直播频道    DeleteLVBChannel</p> </li>     <li> <p>创建录制任务    CreateRecord</p> </li>     <li> <p>终止录制任务    StopRecord</p> </li>     <li> <p>查询已录制分片列表    DescribeRecord</p> </li>     <li> <p>创建截图任务    CreateLVBShot</p> </li>     <li> <p>终止截图任务    StopLVBShot</p> </li>     <li> <p>查看队列消息    DescribeQueueLog</p> </li>    </ul>    <p><strong>腾讯云直播方案整体流程</strong></p>    <p>方案根据腾讯云的快速对接 ,最终形成闭环逻辑。</p>    <ul>     <li> <p><strong>APP</strong></p>      <ol>       <li> <p>视频源推流</p> </li>       <li> <p>向后台发起创建直播频道请求</p> </li>       <li> <p>向后台发起停止直播请求</p> </li>      </ol> </li>     <li> <p><strong>后台</strong></p>      <ol>       <li> <p>向腾讯云发起创建、删除(删除前先关闭)直播频道请求</p> </li>       <li> <p>直播频道缓存队列,处理僵尸频道</p> </li>       <li> <p>向APP客户端推送直播URL</p> </li>      </ol> </li>     <li> <p><strong>Web</strong></p>      <ol>       <li> <p>PC端的流视频播放器</p> </li>       <li> <p>移动客户端的流视频播放器</p> </li>      </ol> </li>    </ul>    <p><strong>流程图</strong></p>    <ul>     <li> <p><strong>Step1:</strong>创建频道</p> <p>客户端发起直播请求,后台调用CreateLVBChannel,由返回的channel_id调用DescribeLVBChannel查看频道信息。后台向客户端返回推流url和Web直播地址(非flv流视频地址)。</p> </li>     <li> <p><strong>Step2:</strong>SDK推流 推流SDK</p> </li>     <li> <p><strong>Step3:删除频道</strong> APP端推流结束,向后台发送请求删除频道,只有关闭的频道是可以删除的,所以后台删除一个频道之前,要先通过停止直播频道接口StopLVBChannel,先将频道状态置为停止,之后在调用删除直播频道接口DeleteLVBChannel对频道进行删除。</p> </li>    </ul>    <p><img src="https://simg.open-open.com/show/53e95056b5266b80fee6f43c5d141c14.jpg"></p>    <h2><strong>四、播放直播视频</strong></h2>    <p>如何看</p>    <p>下载直播视频有以下方式:</p>    <ol>     <li> <p>HLS</p> </li>     <li> <p>rtmp</p> </li>     <li> <p>flv</p> </li>    </ol>    <p>好看的指标参数</p>    <p>码率:影响体积,与体积成正比:码率越大,体积越大;码率越小,体积越小。</p>    <p>帧率:影响画面流畅度,与画面流畅度成正比:帧率越大,画面越流畅;帧率越小,画面越有跳动感。如果码率为变量,则帧率也会影响体积,帧率越高,每秒钟经过的画面越多,需要的码率也越高,体积也越大。</p>    <p>分辨率:影响图像大小,与图像大小成正比:分辨率越高,图像越大;分辨率越低,图像越小。</p>    <h3><strong>1、HLS</strong></h3>    <p>对于H5视频播放,可以使用 HLS(HTTP Live Streaming)协议播放直播流,iOS和 Android 都天然支持这种协议,配置简单,直接使用 video 标签即可。</p>    <p>使用 video在移动客户端上播放直播视频:</p>    <pre>  <video controls autoplay>     <source src="xxx.m3u8" type="application/vnd.apple.mpegurl"/>  </video></pre>    <p><strong>HTTP Live Streaming</strong></p>    <p>HLS是一个由苹果公司提出的基于HTTP的流媒体网络传输协议。</p>    <p>HLS直播最大的不同在于,直播客户端获取到的,并不是一个完整的数据流。</p>    <p>HLS协议在服务器端将直播数据流存储为连续的、很短时长的媒体文件(MPEG-TS格式),而客户端则不断的下载并播放这些小文件,因为服务器端总是会将最新的直播数据生成新的小文件,这样客户端只要不停的按顺序播放从服务器获取到的文件,就实现了直播。</p>    <p>由此可见,基本上可以认为,HLS是以点播的技术方式来实现直播。由于数据通过HTTP协议传输,所以不用考虑防火墙或者代理的问题,而且分段文件的时长很短,客户端可以很快的选择和切换码率,以适应不同带宽条件下的播放。不过HLS的这种技术特点决定了延迟一般总是会高于普通的流媒体直播协议。</p>    <p>每一个 .m3u8 文件,分别对应若干个 ts 文件,这些 ts 文件才是真正存放视频的数据,m3u8 文件只是存放了一些 ts 文件的配置信息和相关路径,当视频播放时,.m3u8 是动态改变的,video 标签会解析这个文件,并找到对应的 ts 文件来播放,所以一般为了加快速度,.m3u8 放在 Web 服务器上,ts 文件放在 CDN 上。</p>    <p>支持的视频流编码为H.264,音频流编码为AAC。</p>    <p>简单讲就是把整个流分成一个个小的,基于 HTTP 的文件来下载,每次只下载一些,前面提到了用于 H5 播放直播视频时引入的一个 .m3u8 的文件,这个文件就是基于 HLS 协议,存放视频流元数据的文件。</p>    <ul>     <li> <p>HLS的分段策略,基本上推荐是10秒一个分片,当然,具体时间还要根据分好后的分片的实际时长做标注</p> </li>     <li> <p>为了缓存等方面的原因,在索引文件中会保留最新的三个分片地址,以“滑动窗口”的形式进行刷新。</p> </li>    </ul>    <p>.m3u8 文件,其实就是以 UTF-8 编码的 m3u 文件,这个文件本身不能播放,只是存放了播放信息的文本文件。</p>    <p>打开之后就是这个样子:</p>    <pre>  #EXTM3U                     m3u文件头,必须放在第一行  #EXT-X-MEDIA-SEQUENCE       第一个TS分片的序列号  #EXT-X-TARGETDURATION       每个分片TS的最大的时长  #EXT-X-ALLOW-CACHE          是否允许cache  #EXT-X-ENDLIST              m3u8文件结束符  #EXTINF                     extra info,分片TS的信息,如时长,带宽等</pre>    <pre>  #EXTM3U  #EXT-X-TARGETDURATION:11#EXT-X-VERSION:3#EXT-X-MEDIA-SEQUENCE:0#EXT-X-PLAYLIST-TYPE:VOD  #EXTINF:10.133333,  fileSequence0.ts  #EXTINF:10.000666,  fileSequence1.ts  #EXTINF:10.667334,  fileSequence2.ts  #EXTINF:9.686001,  fileSequence3.ts  #EXTINF:9.768665,  fileSequence4.ts  #EXTINF:10.000000,  fileSequence5.ts  #EXT-X-ENDLIST</pre>    <p>ts 文件,就是存放视频的文件:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/f05da86d6b252d732ae64aadc084d115.jpg"></p>    <p>HLS只请求基本的HTTP报文,与实时传输协议(RTP)不同,HLS可以穿过任何允许HTTP数据通过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。</p>    <p><strong>HLS 的请求播放流程</strong></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/38e95af9df403ba75181b69b366aa183.png"></p>    <ol>     <li> <p>HTTP 请求 m3u8 的 url。</p> </li>     <li> <p>服务端返回一个 m3u8 的播放列表,这个播放列表是实时更新的,一般一次给出5段数据的 url。</p> </li>     <li> <p>客户端解析 m3u8 的播放列表,再按序请求每一段的 url,获取 ts 数据流。</p> </li>    </ol>    <p><strong>HLS直播延时</strong></p>    <p>我们知道 hls 协议是将直播流分成一段一段的小段视频去下载播放的,所以假设列表里面的包含5个 ts 文件,每个 TS 文件包含5秒的视频内容,那么整体的延迟就是25秒。因为当你看到这些视频时,主播已经将视频录制好上传上去了,所以时这样产生的延迟。当然可以缩短列表的长度和单个 ts 文件的大小来降低延迟,极致来说可以缩减列表长度为1,并且 ts 的时长为1s,但是这样会造成请求次数增加,增大服务器压力,当网速慢时回造成更多的缓冲,所以苹果官方推荐的 ts 时长时10s,所以这样就会大改有30s的延迟。所以服务器接收流,转码,保存,切块,再分发给客户端,这里就延时的根本原因。</p>    <p><strong>HLS优势</strong></p>    <p>但是 H5 直播视频却有一些不可替代的优势:</p>    <ol>     <li> <p>传播性好,利于分享等操作。</p> </li>     <li> <p>可以动态发布,有利于实时迭代产品需求并迅速上线。</p> </li>     <li> <p>不用安装 App,直接打开浏览器即可。</p> </li>    </ol>    <h3><strong>2、RTMP</strong></h3>    <p>RTMP协议是应用层协议,是要靠底层可靠的传输层协议(通常是TCP)来保证信息传输的可靠性的。在基于传输层协议的链接建立完成后,RTMP协议也要客户端和服务器通过“握手”来建立基于传输层链接之上的 <strong>NetConnection</strong> 链接,在Connection链接上会传输一些控制信息,如SetChunkSize,SetACKWindowSize。其中CreateStream命令会创建一个 <strong>NetStream</strong> 链接,用于传输具体的音视频数据和控制这些信息传输的命令信息。他们的关系如图所示:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a0b7f630f99a21be19aa69ddf088bac1.jpg"></p>    <p><strong>RTMP Message</strong></p>    <p>RTMP协议传输时会对数据做自己的格式化,这种格式的消息我们称之为RTMP Message,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID的Chunk,每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接受端会根据chunk中包含的data的长度,message id和message的长度把chunk还原成完整的Message,从而实现信息的收发。</p>    <p><img src="https://simg.open-open.com/show/0d6ffb7932d777e4c8131da032ff71aa.jpg"></p>    <ol>     <li> <p><strong>Basic Header</strong>(基本的头信息)</p> <p>chunk stream ID(流通道Id)和chunk type(chunk的类型,2位fmt),chunk stream id一般被简写为CSID,用来唯一标识一个特定的流通道,chunk type决定了后面Message Header的格式。长度有1、2或3个字节</p> </li>     <li> <p><strong>Message Header</strong>(消息的头信息)</p> <p>Message Header的格式和长度取决于Basic Header的chunk type,共有4种不同的格式,由上面所提到的Basic Header中的fmt字段控制。包含timestamp,timestamp delta,message length,message type id,msg stream id,和0(表示与上一个相同)。</p>      <ul>       <li> <p>timestamp(时间戳):占用3个字节,因此它最多能表示到16777215=0xFFFFFF=2</p> <p>24-1, 当它的值超过这个最大值时,这三个字节都置为1,这样实际的timestamp会转存到Extended Timestamp字段中,接受端在判断timestamp字段24个位都为1时就会去Extended timestamp中解析实际的时间戳。</p> </li>       <li> <p>message length(消息数据的长度):占用3个字节,表示实际发送的消息的数据如音频帧、视频帧等数据的长度,单位是字节。注意这里是Message的长度,也就是chunk属于的Message的总数据长度,而不是chunk本身Data的数据的长度。</p> </li>       <li> <p>message type id(消息的类型id):占用1个字节,表示实际发送的数据的类型,如8代表音频数据、9代表视频数据。</p> </li>       <li> <p>msg stream id(消息的流id):占用4个字节,表示该chunk所在的流的ID,和Basic Header的CSID一样,它采用小端存储的方式,</p> </li>      </ul> </li>     <li> <p><strong>Extended Timestamp</strong>(扩展时间戳)</p> <p>4个字节,当扩展时间戳启用时,timestamp字段或者timestamp delta要全置为1,表示应该去扩展时间戳字段来提取真正的时间戳或者时间戳差。注意扩展时间戳存储的是完整值,而不是减去时间戳或者时间戳差的值。</p> </li>     <li> <p><strong>Chunk Data</strong>(块数据)</p> <p>用户层面上真正想要发送的与协议无关的数据,长度在(0,chunkSize]之间。</p> </li>    </ol>    <p>RTMP在收发数据的时候并不是以Message为单位的,而是把Message拆分成Chunk发送,而且必须在一个Chunk发送完成之后才能开始发送下一个Chunk。每个Chunk中带有MessageID代表属于哪个Message,接受端也会按照这个id来将chunk组装成Message。</p>    <p><strong>举个例子</strong></p>    <p><strong>chunk表示例1</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d55785860b0ff689fce57efc3313cdbb.jpg"></p>    <p>首先包含第一个Message的chunk的Chunk Type为0,因为它没有前面可参考的chunk,timestamp为1000。type为0的header占用11个字节,假定CSID为3<127,因此Basic Header占用1个字节,再加上Data的32个字节,因此第一个chunk共44=11+1+32个字节。</p>    <p>第二个chunk和第一个chunk的CSID,TypeId,Data的长度都相同,因此采用Chunk Type=2,timestamp delta=1020-1000=20,因此第二个chunk占用36=3+1+32个字节。</p>    <p>第三个chunk和第二个chunk的CSID,TypeId,Data的长度和时间戳差都相同,因此采用Chunk Type=3省去全部Message Header的信息,占用33=1+32个字节。</p>    <p>第四个chunk和第三个chunk情况相同,也占用33=1+32个字节。</p>    <p>最后实际发送的chunk如下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/7dd00aec00aa01abf40998f6dc617f1b.jpg"></p>    <p><strong>chunk表示例2</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c14b5b5f810344a7d84d4f81edc8eebc.jpg"></p>    <p>注意到Data的Length=307>128,因此这个Message要切分成几个chunk发送,第一个chunk的Type=0,Timestamp=1000,承担128个字节的Data,因此共占用140=11+1+128个字节。</p>    <p>第二个chunk也要发送128个字节,其他字段也同第一个chunk,因此采用Chunk Type=3,此时时间戳也为1000,共占用129=1+128个字节。</p>    <p>第三个chunk要发送的Data的长度为307-128-128=51个字节,还是采用Type=3,共占用1+51=52个字节。</p>    <p>最后实际发送的chunk如下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/b71572f7353dc4a887a2e988a400d878.jpg"></p>    <p>Q:为什么RTMP要将Message拆分成不同的Chunk呢?</p>    <p>A:通过拆分,数据量较大的Message可以被拆分成较小的“Message”,这样就可以避免优先级低的消息持续发送阻塞优先级高的数据,比如在视频的传输过程中,会包括视频帧,音频帧和RTMP控制信息,如果持续发送音频数据或者控制数据的话可能就会造成视频帧的阻塞,然后就会造成看视频时最烦人的卡顿现象。同时对于数据量较小的Message,可以通过对Chunk Header的字段来压缩信息,从而减少信息的传输量。</p>    <p><strong>阻塞 vs. CPU</strong></p>    <p>Chunk的默认大小是128字节,在传输过程中,通过一个叫做Set Chunk Size的控制信息可以设置Chunk数据量的最大值,在发送端和接受端会各自维护一个Chunk Size,可以分别设置这个值来改变自己这一方发送的Chunk的最大大小。大一点的Chunk减少了计算每个chunk的时间从而减少了CPU的占用率,但是它会占用更多的时间在发送上,尤其是在低带宽的网络情况下,很可能会阻塞后面更重要信息的传输。小一点的Chunk可以减少这种阻塞问题,但小的Chunk会引入过多额外的信息(Chunk中的Header),少量多次的传输也可能会造成发送的间断导致不能充分利用高带宽的优势,因此并不适合在高比特率的流中传输。在实际发送时应对要发送的数据用不同的Chunk Size去尝试,通过抓包分析等手段得出合适的Chunk大小,并且在传输过程中可以根据当前的带宽信息和实际信息的大小动态调整Chunk的大小,从而尽量提高CPU的利用率并减少信息的阻塞机率。</p>    <p>播放一个RTMP协议的流媒体需要经过以下几个步骤:握手,建立连接,建立流,播放。RTMP连接都是以握手作为开始的。建立连接阶段用于建立客户端与服务器之间的“网络连接”;建立流阶段用于建立客户端与服务器之间的“网络流”;播放阶段用于传输视音频数据。</p>    <p><strong>握手(HandShake)</strong></p>    <p>一个RTMP连接以握手开始,双方分别发送大小固定的三个数据块</p>    <ol>     <li> <p>握手开始于客户端发送C0、C1块。服务器收到C0或C1后发送S0和S1。</p> </li>     <li> <p>当客户端收齐S0和S1后,开始发送C2。当服务器收齐C0和C1后,开始发送S2。</p> </li>     <li> <p>当客户端和服务器分别收到S2和C2后,握手完成。</p> </li>    </ol>    <p><img src="https://simg.open-open.com/show/ca33155e5e02af1eced255faa3c8547f.jpg"></p>    <p>理论上来讲只要满足以上条件,如何安排6个Message的顺序都是可以的,但实际实现中为了在保证握手的身份验证功能的基础上尽量减少通信的次数,一般的发送顺序是这样的:</p>    <ol>     <li> <p>Client发送C0+C1到Sever</p> </li>     <li> <p>Server发送S0+S1+S2到Client</p> </li>     <li> <p>Client发送C2到Server,握手完成</p> </li>    </ol>    <p><strong>建立网络连接(NetConnection)</strong></p>    <ol>     <li> <p>客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。</p> </li>     <li> <p>服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序。</p> </li>     <li> <p>服务器发送设置带宽(Set Peer Bandwitdh)协议消息到客户端。</p> </li>     <li> <p>客户端处理设置带宽协议消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到服务器端。</p> </li>     <li> <p>服务器发送用户控制消息中的“流开始”(Stream Begin)消息到客户端。</p> </li>     <li> <p>服务器发送命令消息中的“结果”(_result),通知客户端连接的状态。</p> </li>     <li> <p>客户端在收到服务器发来的消息后,返回确认窗口大小,此时网络连接创建完成。</p> </li>    </ol>    <p>服务器在收到客户端发送的连接请求后发送如下信息:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/203fbdde599f5e853de5ac261cc1f289.jpg"></p>    <p>主要是告诉客户端确认窗口大小,设置节点带宽,然后服务器把“连接”连接到指定的应用并返回结果,“网络连接成功”。并且返回流开始的的消息(Stream Begin 0)。</p>    <p><strong>建立网络流(NetStream)</strong></p>    <ol>     <li> <p>客户端发送命令消息中的“创建流”(createStream)命令到服务器端。</p> </li>     <li> <p>服务器端接收到“创建流”命令后,发送命令消息中的“结果”(_result),通知客户端流的状态。</p> </li>    </ol>    <p><strong>推流流程</strong></p>    <ol>     <li> <p>客户端发送publish推流指令。</p> </li>     <li> <p>服务器发送用户控制消息中的“流开始”(Stream Begin)消息到客户端。</p> </li>     <li> <p>客户端发送元数据(分辨率、帧率、音频采样率、音频码率等等)。</p> </li>     <li> <p>客户端发送音频数据。</p> </li>     <li> <p>客户端发送服务器发送设置块大小(ChunkSize)协议消息。</p> </li>     <li> <p>服务器发送命令消息中的“结果”(_result),通知客户端推送的状态。</p> </li>     <li style="text-align: center;"> <p>客户端收到后,发送视频数据直到结束。</p> <img src="https://simg.open-open.com/show/e35efa0f31591bfd299fcade60bdda64.jpg"></li>    </ol>    <p><strong>播流流程</strong></p>    <ol>     <li> <p>客户端发送命令消息中的“播放”(play)命令到服务器。</p> </li>     <li> <p>接收到播放命令后,服务器发送设置块大小(ChunkSize)协议消息。</p> </li>     <li> <p>服务器发送用户控制消息中的“streambegin”,告知客户端流ID。</p> </li>     <li> <p>播放命令成功的话,服务器发送命令消息中的“响应状态” NetStream.Play.Start & NetStream.Play.reset,告知客户端“播放”命令执行成功。</p> </li>     <li style="text-align: center;"> <p>在此之后服务器发送客户端要播放的音频和视频数据。</p> <img src="https://simg.open-open.com/show/388d6744a091decb38eb112118014091.jpg"></li>    </ol>    <h2><strong>五、直播中的用户交互</strong></h2>    <p>对于直播中的用户交互大致可以分为:</p>    <ol>     <li> <p>送礼物</p> </li>     <li> <p>发表评论或者弹幕</p> </li>     <li> <p>对于送礼物,在 H5 端可以利用 DOM 和 CSS3 实现送礼物逻辑和一些特殊的礼物动画,实现技术难点不大。</p> </li>    </ol>    <p>对于弹幕来说,要稍微复杂一些,可能需要关注以下几点:</p>    <ol>     <li> <p>弹幕实时性,可以利用 webscoket 来实时发送和接收新的弹幕并渲染出来。</p> </li>     <li> <p>对于不支持 webscoket 的浏览器来说,只能降级为长轮询或者前端定时器发送请求来获取实时弹幕。</p> </li>     <li> <p>弹幕渲染时的动画和碰撞检测(即弹幕不重叠)等等</p> </li>    </ol>    <h3><strong>Html5直播聊天室组件</strong></h3>    <p>该组件主要适用于基于Html5的web 大群互动直播场景。具备如下特点:</p>    <p>1)支持匿名身份入群,粉丝与主播进行亲密互动</p>    <p>2)支持多人聊天,主播同一个帐号多标签页收发消息,粉丝再多也不用愁</p>    <p>3)支持多种聊天方式,文本,表情,红包,点赞,想怎么互动就怎么互动</p>    <p>4)支持不同优先级消息的频率控制,一键在手,权利尽在掌握中</p>    <p>5)对互动直播场景进行了专门的优化,参与人数多,消息量再大也能从容应对</p>    <h3>前端技术点</h3>    <ol>     <li> <p>秒开</p> </li>     <li> <p>时延</p> </li>     <li> <p>流畅</p> </li>     <li> <p>清晰度</p> </li>    </ol>    <h2><strong>六、总结</strong></h2>    <p>目前较为成熟的直播产品,大致都是以 Server 端、 H5直播前端 和 Native(Android,iOS)搭配实现直播。</p>    <p>主要从android客户端出发,从最初的录制视频到客户端观看直播的整个流程,给出了各个技术点的概要和解决方案,从0到1完成了简单的直播实现。从0到1易,从1到100还有更多的技术细节有待研究。</p>    <p> </p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653578065&idx=1&sn=1da088b11c797d2be7b5f7afb8f056bb&chksm=84b3b156b3c438401715dc40f7791010b11050983a67fa533a4b559cb67ea6d0ce1996970481&scene=0</p>    <p> </p>