套式语段

以科学家的精神,建设性的态度,做好专业的研究,功过是非交给时间和历史。哪有什么优秀,不过是更专注,更勤奋,更热爱。所谓的伟大,不过是简单,正直,目标远大,坚韧不拔。能经受多大的诋毁,才能经受多大的赞誉。不在荣誉面前停留,不为失败辩解,一直在路上,风雨兼程。

严把征拆资金事前“审批程序关”、事中“使用控制关”、事后“监督检查关”,引进财政、审计部门强化体外监督,保证征拆补偿公开、公平、公正。

今年以来,湘潭深入贯彻习近平总书记“疫情要防住、经济要稳住、发展要安全”重要指示要求,认真落实党中央、国务院和省委、省政府决策部署,强化“新官要理旧账”担当,聚焦发展和安全两件大事,突出长短结合、标本兼治,打出科学化债、精准化债系列组合拳,牢牢守住不发生区域性系统性金融风险底线,全力守护毛主席家乡安全稳定。下一步,湘潭将以这次调研座谈为契机,强化“时时放心不下”的政治责任感,统筹抓好发展和安全两件大事,弘扬“三牛”精神,大干、快干、抓紧干、拼命干,坚决维护毛主席家乡的安全稳定和良好形象。

会议要求,全体五粮液人要坚决贯彻落实市委、市政府各项决策部署,对标对表“实心干事、科学作为”的精神和要求,坚持品质为基、文化铸魂、守正创新,确保新一年各项工作开好局、起好步。

对标对表强担当,切实将防范化解地方政府债务风险放在心上、扛在肩上,统筹发展和安全,认清形势、坚定信心,将制度优势转化为治理效能。要紧盯目标守底线,依靠良性化债的新思路,打开经济社会高质量发展新天地。聚焦债务风险防化,压紧压实属地监管责任,建立健全问题倒查和责任追究机制;聚焦平台公司转型,提升内生动力;聚焦专项债券投资效益,发挥债券资金带动扩大有效投资的作用。要强化监督重保障,推动政府债务管理在法治轨道上规范、安全、高效运行,在严格遵守法定程序中落实法律责任,在发挥法律刚性约束中强化监督实效,在总结经验做法中优化制度供给,实现经济社会持续健康发展。

不过,作为全球电商平台的头部代表,亚马逊在这些问题上所遭受的非议显然具备更深层次意义,这说明它对于市场、客户与消费者的控制力正在不断减弱。

以科学家的精神,建设性的态度,做好专业的研究,功过是非交给时间和历史。哪有什么优秀,不过是更专注,更勤奋,更热爱。所谓的伟大,不过是简单,正直,目标远大,坚韧不拔。能经受多大的诋毁,才能经受多大的赞誉。不在荣誉面前停留,不为失败辩解,一直在路上,风雨兼程。

经研究,你单位提供的福建省泉州市中级人民法院刑事裁定书等材料未改变《福建省泉州市欣佳酒店“3·7”坍塌事故调查报告》认定的你单位的违法事实和责任,故决定不予采纳你单位的申辩意见。

魏彬身为巡视机构党员领导干部,政治上蜕化变质,毫无理想信念,背离“两个维护”,背弃初心使命,丧失党性原则,对党极不忠诚、不老实,处心积虑对抗组织审查;经济上贪婪成性,价值观极度扭曲,信奉金钱至上,对纪法毫无敬畏之心,把公权力当作谋取私利的工具,大肆敛财;生活上腐化堕落,内心阴暗,精神空虚,道德败坏,寡廉鲜耻,大搞钱色交易,严重破坏被巡视地区政治生态,严重损害党的事业和党员领导干部形象。其行为已严重违纪违法,并涉嫌受贿、利用影响力受贿犯罪,且在党的十八大乃至党的十九大后仍不收敛、不收手,拒不认错悔罪,性质极其严重,影响极其恶劣,

主要有四点体会:讲成就催人奋进、讲政策方向明确、讲经验弥足珍贵、讲发展信心十足。”毛伟明说,会议强调的“五个必须”,既是新时代经济工作的规律性认识,更是今后一个时期经济工作的重要方法和指导,使我们在思想上廓清了迷雾,在政治上增强了定力,在方法上掌握了规律,在行动上充满了力量。

如何把中央经济工作会议精神和湖南实际结合起来,创造性的贯彻落实好?千方百计将中央的政策红利转化为湖南的现实生产力。全省经济运行呈现逐月向好、稳中有进、进中提质态势

洞察时与势,把握危与机。毛伟明从在方向指引上把牢根本点、在政策取向上找准着力点、在政策机遇上把握切入点等三个方面娓娓道来

https://mp.weixin.qq.com/s/RA1LaUxbqUX20aBFujE3eg

改革要谋长远之策、蓄未来之势。进一步全面深化改革,湘潭从何始、发何力、如何改?市委十三届八次全会通过的《决定》,突出“要我改”和“我要改”相结合,突出“以制度变革引领模式创新,以模式创新引领高质量发展”这个着力点和发力方向,突出以经济体制改革为牵引,突出整体谋划和重点突破相结合,突出加强党对进一步全面深化改革的领导,按照“中央有要求、省委有部署、湘潭有需要、群众有期盼、近期可见效”,明确了全市进一步全面深化改革的目标任务、主攻方向和关键举措,分类同步部署了两批共16项重点改革事项,为我们全力抓落实提供了清晰思路。

改革一程接一程,必须把准方向。

改革千头万绪,必须找准方法。

改革千难万险,必须激扬锐气。

又踏层峰辟新天,更扬云帆立潮头。

人类抽象化

这是一个趋势,越来越多的人已脱离了原始的人类生活,把自己抽象成了一个宠物,在更好的解决了温饱后,有一部分人不用再进行具体的体力劳动,生活从具体的社会劳动中抽象,每天的生活就是交际,上班,或者娱乐。

美升息影响

股票市场最怕升息,很多上市公司都有借款,利率提高了,它成本就上去了,以后报表不好看,
美国可能通过快速升息,并加大幅度,这种反常的行为,从而抽取全球的资金,反哺自己的经济。美国加息越快,越多,对比其它国家的息差就越大,下的饵就越有吸引力!
因为息差是无风险(国家不可能倒闭),因此会吸引国际大规模的资金,比如主权基金,养老基金,甚至保险资金涌入美国。
这些资金进入美国,也不会呆着不动,会投资实业,或买入债券,股票等,所以美股因为流动性增加而脱险,美国经济因为全球资金回流,全球投资增加而获得喘息的机会,相反美国之外的经济,可能遭殃。
黄金近期价格出现快速下滑,原因也是美元升息,黄金作为无息的资产,美元利率越高,黄金吸引力越小。

正向扩张循环结束

中国是个强政府类型,从土地金融,土地财政开始有钱,政府支出增加,推动经济扩张正向循环,但现在开始地方政府财政困难,扩张行成的正向良性循环结束。经济将开始进入收缩的痛苦

有的政策影响深远,如从土地金融开始了房地产爆发,现在什么能接替房地产继续接纳房地产这个量级的资产增值需求?可能是股市吗?

PON网络数据传输方式

那在OLT与POS之间其实只有一根光纤,如何实现上下行的数据在一根光纤中传输呢?

【答案】:划分不同的车道,即规划不同的波长。

【解析】:在PON网络中,上下行通信所采用的波长是不一样的,比如EPON/GPON同属于1G PON技术,它们下行波长规划的是1490nm,上行波长规划的是1310nm,互不影响,完全可以实现双向全双工的通信。此外,还有广电的CATV业务,单独的波长规划时1550nm。

到这里,有小伙伴又有疑问了:

为什么规定下行波长是1490nm,上行是1310nm,而不是反过来呢?

【答案】:成本考虑。

【解析】:标准制定的时候,1310nm的光器件已经相对来说比较成熟,成本较低,而1490nm成本较高。PON是一个点到多点的架构,那么我们自然要将成本比较低器件放在多点侧的发端,这样才会大量的降低PON网络初期的部署成本。

下面我们继续回到PON网络的数据传输的介绍。PON下行采用广播的方式传输数据,上行采用的是TDMA的方式传输数据。一连串的疑问又来了:

PON下行为何采用广播的方式传输数据呢?

【答案】:因为简单实用。

【解析】:PON的下行方向是指从OLT到ONU的这个方向,这个方向发端与收端的数量对比关系是“1对多”的关系,那我们自然就会选择用广播的方式来发送数据,因为这样最省事。同时,下行因受限于无源分路器的物理特性,PON口发出的数据经过无源分路器后平均分配至每一分路,其无源物理特性无法控制某一分路的通与不通,仅能实现单纯分路功能,故下行依据物理分路特性实现了被动广播的现象。

PON下行采用广播,每个ONU都能收到其他ONU的数据,如何保证数据安全?

【答案】:ONU主动过滤,同时数据有加密。

【解析】:一方面,ONU会根据相应的过滤条件主动过滤属于自己的数据,如通过ONU ID(GEM-PORT ID)过滤接收属于自己的数据;另一方面,OLT给每个ONU发送的数据会进行加密(如GPON的AES-128),且加密的密钥是由每个ONU产生并发给OLT的,ONU不会知道其他ONU的密钥,故也难解密属于其他ONU的数据。

动图封面

PON上行为何采用TDMA的方式传输数据呢?

【答案】:实现多ONU同时传输数据,公平竞争。

【解析】:PON的上行方向跟下行方向是反的,即指ONU到OLT的方向,这个方向发端与收端的数量对比关系是“多对一”的关系。既然是“多对一”的关系,肯定就不能让每个ONU想当然的发送数据了,否则就会存在同波长光信号叠加(如GPON的1310nm),OLT接收后也无法读取数据,出现误码帧或未知帧提示,并将其丢弃。

所以这种情况就必须要有一种仲裁机制,来保护我们的上行数据传输不发生冲突。这个仲裁机制便是TDMA,它将上行链路分成不同的时隙,再将这些时隙根据需要分给不同的ONU,ONU在属于自己的时隙发送数据。

动图封面

因此,我们这里也看到,ONU的发光时间严格受OLT指定,它是不会主动发光的,也不会长时间发光的。一旦某个ONU主动发光了或者长时间发光了,这个ONU就是一个“流氓ONU”,会影响整个PON口下的业务。

视频切片加密后播放

最近网上大学想下载一个视频,但下载下来的ts文件不能播放,把m3u8文件下载下来一看里面有

#EXT-X-KEY:METHOD=AES-128,URI="https://kc.zhixueyun.com/hls/9fd5d229-3b75-11e7-b669-44a84230d603",IV=0xd9221589c954ff5538bdc9af225e3e9e

里面的EXT-X-KEY:METHOD=AES-128说明服务器方在把文件切片成ts文件然后播放时进行了加密,所以我们先把m3u8这个文件下载下来,看有多少个ts文件,然后要把uri这个文件和所有ts文件下载下来(迅雷批量下载)放一个文件夹,把上面文件中的uri地址改成本地地址,用迅雷影音就可以直接打开m3u8文件播放。

一般hls视频流加密我们用到常见的一种是防盗链(严格来讲这不属于加密) , 也就是说给 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文件是动态生成而并非静态文件

另一种就是我上面说的DRM加密,用来给ts内容加密, m3u8有这个tag: #EXT-X-KEY

下面是网上找的如何把一个mp4视频使用ffmpeg视频切片并加密和视频AES-128加密后播放

创建加密文件:将一个mp4视频文件切割为多个ts片段,并在切割过程中对每一个片段使用AES-128 加密,最后生成一个m3u8的视频索引文件;

1.加密用的key(文件则保存当前目录)

指令:openssl rand -base64 20 > enc.key

提示打开文件本次生成的 n4DHLx7kMPeewvW3dGlm5i/EE8I=

2.另一个是iv(生成一段字符串,记下来):

指令:openssl rand -hex 16

提示打印出本次生成的682f5033538cf71567e1bdb38f5f9a07

新建一个文件enc.keyinfo 内容格式如下:
Key URI # enc.key的路径,使用http形式
Path to key file # enc.key文件
IV # 上面生成的iv

实例:
http://edu.gamagou.cn/enc.key
/usr/share/nginx/html/enc.key
682f5033538cf71567e1bdb38f5f9a07

  1. ffmpeg加密指令:

/home/programs/video/ffmpeg/ffmpeg -y -i /home/programs/video/test/123.mp4 -hls_time 12 -hls_key_info_file /home/programs/video/conf/enc.keyinfo -hls_playlist_type vod -hls_segment_filename “file%d.ts” playlist.m3u8

加密后的文件形式:

EXTM3U
EXT-X-VERSION:3
EXT-X-TARGETDURATION:12
EXT-X-MEDIA-SEQUENCE:0
EXT-X-PLAYLIST-TYPE:VOD
EXT-X-KEY:METHOD=AES-128,URI=”https://edu.amin.cc/enc.key”,IV=0x15de9b7283a69d7ef11f6f12e488fbb7
EXTINF:12.040000,

file0.ts

EXTINF:10.440000,

file1.ts

EXT-X-ENDLIST
  1. 播放部分:
    jq控件:hls.js
    手册:https://www.bootcdn.cn/hls.js/readme/

播放页面:

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<!-- Or if you want a more recent canary version -->
<!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@canary"></script> -->
<video id="video"></video>
<script>
  var video = document.getElementById('video');
  if(Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource('https://替换你的文件路径.m3u8');
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED,function() {
      video.play();
  });
 } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = 'https://替换你的文件路径.m3u8';
    video.addEventListener('loadedmetadata',function() {
      video.play();
    });
  }
</script>

完成。


裸纤、专线、SDH、MSTP、MSTP+、OTN、PTN、IP-RAN

(一)裸纤
裸纤也叫裸光纤,运营商提供一条纯净光纤线路,中间不经过任何交换机或路由器,只经过配线架或配线箱做光纤跳纤,可以理解成运营商仅仅提供一条物理线路。实际项目中,裸光纤应用较多,比如某大学两个校区,相隔大概20KM,租用运营商裸光纤实现两个校区互联。可以理解成通过一根很长的光纤连接两个校区,拓扑图也简单,直连即可,如图所示:

最早两个校区均有运营商互联网出口,采用两套认证系统,管理维护麻烦,后续升级改造,两个校区租用运营商裸光纤,将两个校区互联起来,相当于将老校区网络作为一个子模块,接入到新校区,与新校区有机融合到一起,实现统一运营和管理。

租用运营商裸纤价格较高,一般按照公里收费,记得某项目租用20km裸纤,费用为20万/年,5年线路费用就是100万。有人可能会问,既然裸纤线路这么贵,为什么不自己拉一根光纤,连接两个校区,买光纤和工人布线施工的成本也用不了这么多呀!的确如此,但不是你想拉光纤就能拉的。国家法律明文规定,只有运营商、军队、市政等几个部门可以在公共区域破土施工,学校/医院/政府这类单位,在自己单位园区内部(也就是围墙内)随便怎么挖,没人会管,但到公共区域施工就不允许了,犯法!

裸光纤应用场景还很多,比如教育城域网,会租用运营商裸光纤,实现教育局与辖区各学校互联,将网络出口统一到教育局,从而实现教育资源共享,统一审计等功能,某县教育网城域网拓扑如图,租用运营商裸光纤实现学校与教育局互联,最后通过教育局网络统一网络出口访问互联网。

另外,在平安城市/视频专网/雪亮工程等项目中,也经常租用运营商裸纤,实现前端摄像头视频流量回传,如下为公安部统计,全国视频专网链路类型,35%采用裸纤。(注:交警前端电子警察一般采用裸纤回传,有钱就任性,而平安城市摄像头采用PON链路较多)

(二)专线
专线一般分三层专线和二层专线,三层专线一般是指MPLS V.P.N,这是CCIE RS方向考试的重点内容,说实话比较难,基本只在金融、电子政务等行业应用,一般人接触不上。我们日常说的专线基本指二层专线,二层专线相当于运营商给了你“一根线”,你两端连接PC,分别配置192.168.1.1/24和192.168.1.2/24,能直接ping通。效果看似跟裸纤差不多,但与裸纤的实质区别是: 裸纤中间不经过任何路由器交换机设备,运营商给你的是真实的一根线,而专线中间经过运营商的各类路由器交换机设备,只是运营商给你模拟出来了一根线,让你感觉跟裸纤效果差不多,至于专线的底层技术,这个是CCIE ISP运营商方向研究的内容,大部分人无需搞得那么透。

如果租用运营商裸纤,只要光纤不被挖断,运营商内部交换机路由器等设备故障,不会影响业务。如果你租用的光纤被挖断了,那就没办法了,业务肯定受影响。如果租用运营商专线,运营商设备出问题,可能影响用户业务,反而光纤被挖断,不一定会影响业务,因为运营商骨干网采用环形设计,传统SDH和新的OTN底层都会有环网,所以论可靠性而言,专线可靠性稍高。笔者今年经历的一个大型雪亮工程项目,一个主数据中心,七个分中心,考虑使用什么链路互联。最早规划使用裸纤互联,但后来考虑到可靠性,改成了租用运营商二层专线互联。当然,使用裸纤也有别的优势,比如带宽自主可控,如下图所示,你需要10G带宽,只需要在交换机上插10G光模块,需要40G带宽在交换机上插40G光模块,在交换机上插100G光模块,自然就有100G带宽。但是如果向运营商租用10G或40G专线,价格高得吓人!

专线可以看成是运营商的一种产品,底层实现技术很多,有SDH、MSTP、PTN、OTN等。下面为大家介绍一下运营商网络常见术语。

(三)SDH和MSTP
要明白SDH,首先要说TDM,TDM是时分复用,就是将一个标准时长(1 秒)分成若干段小的时间段(8000),每一个小时间段(1/8000=125us)传输一路信号;SDH系统就是传统的电路调度,电路调度是以TDM为基础,。早年的网络都是传输语音的,说白了电话网络的基础是TDM。
在SDH大红大紫的时候,另一场战争以太网和ATM(不是取款机哟)大战中,以太网取得全面胜利,从而以太网大行其道,其中又以IP最为强势,导致今天很多业务侧都IP 化了,服务器、PC、摄像头都得配置IP地址吧!
早年运营商骨干网SDH是大红人(运营商早期就是靠装电话挣钱),而园区网内部以太网是另一个大红人(上网数据通过以太网承载),能否合作一下?于是一拍即合,MSTP诞生,运营商骨干部分依旧使用SDH时分复用,但运营商甩给用户的链路接口采用园区网流行的以太网接口,也就是10M、100M、1G、10G这样的标准以太网接口,而不是155M、622M、2.5G这类POS/CPOS接口。

在合资公司MSTP中股份分配不太均匀:SDH 占股70%,以太网占股20%,其它包括ATM占股10%,掌权的还是SDH,内核还是TDM。以太网和ATM因为股权问题,都没有拿出像样的东西,只是须有其表(提供相应接口而已)毕竟早期网络,还是语音为主,数据为辅,但现在完全颠倒,以太网当然不干了,也就有了后来的WDM、OTN、IP-RAN。(就像一家公司,最牛逼的某销售贡献了90%的业绩,他还不出去自己创业,他是傻么?)

(四)波分WDM
随着互联网的大力普及,电脑、手机、电视等终端都能上网了,带宽的需求急剧增加,电信运营商们赚钱的机会来了,但挑战也来了,以前1*155M可以供好上千人打电话,现在人们需要打电话同时还要上网,家庭宽带都300M光纤入户了,带宽需求增长和现网资源出现矛盾,承载语音的TDM骨干网已经完全不能满足海量音视频传输要求。
要解决这个矛盾,专家们思考良久,某天看到二环路高架桥,恍然大悟,如果将网络传输也划分多个通道,也能提升传输性能。于是波分产生,波分WDM 就是将多个波道的信号放到同一条光纤中进行传送。WDM得到重用后,各地纷纷仿效,现在的WDM不仅在城市主干道里使用(城域波分),还用在跨市、跨省道路上(长途波分)。

裸光纤+波分的效果对比
如图使用传统裸光纤,目前商用较多的是100G光模块,那么链路最大100G。裸光纤两端部署波分设备,带宽惊人,线路带宽直接可以到4T甚至更高,单条光纤目前实验室最高波分速率是36T。当然,波分设备特别贵,不是一般单位买得起,波分设备厂商主要有华为、烽火等,客户为运营商、公安、BAT这类单位居多。

(五)OTN
OTN(OpticalTransportNetwork,光传送网)
WDM波分技术优势是带宽大,大得惊人。当然也存在一些问题,最突出的是实时流量监测和重点业务保障没有很好的措施。
SDH 笑道:小样儿,容量那么大,你监测和管理上还是有问题吧?
WDM 回应道:我容量确实比你大得多,但管理这方面的确没你们做得好;
于是他们握手言欢,优势互补,一个全新的制度诞生了—OTN。
所以简单的说:OTN=WDM+SDH

我们对以前的红人SDH在江湖的发展做了详细的描述,现在的SDH也只相当于OTN掌门下的一个堂主而已了,那么另一位红人以太网它现在发展的如何呢?

(六)MSTP+
由于先前MSTP成立时,股权分配不均,有很多遗留问题,以太网仅占20%,导致现在以太网严重不满意,毕竟现在网络上IP流量占了90%以上,为了安抚这个“销量”占公司90%的“销售”,SDH 集团研究后推出MSTP+(也叫Hybrid MSTP),50/50股权分配,进一步提升以太网在公司的地位,也不为一种好的补偿措施。

(七)IP-RAN和PTN
以太网现在声势越来越大,再加上又有MPLS 助阵,逐渐有了可以抗衡SDH的实力,面对MSTP+50%的股份分配,以太网并不高兴,发誓要有所改观……

为了对抗SDH阵营,以太网大力发展自己的势力,走农村包围城市的策略,先将末端IP化(终端PC、摄像头、打印机都得配IP)。同时建立以IP+以太网主导的骨干网,相当于自己开公司,创业了。以太网阵营,自由散漫惯了,现在出现了两种大的分歧:
第一种:认为我们自己成立的运输公司不让SDH的客户(TDM业务)上车,如果一定要进来,必须改头换面-伪装(仿真),同时我们没有时间上的保证(无时间同步),我们纯粹为我们以太网服务,我们的公司名叫IP-RAN;
第二种:认为我们应该吸收一些SDH的客户,SDH经营了这么多年,它的客户还是很多的(还有很多TDM业务需求),同样进来后还是要改头换面-伪装(仿真),然后再我们的帮派里活动,出帮派后再去掉伪装还原成自己原来的模样,这个公司取名叫PTN。
无论哪种方式,伪装-易容术总少不了,随后就开发了PWE3 易容术(CCIE ISP运营商方向重点讲的内容,一般人了解即可)。

(八)总结
MSTP+(Hybrid MSTP)可以看做是SDH向以太网的妥协方案,不得已而为之。随着相互学习融合,IP-RAN和PTN现在已趋于一致,差别不大了,所谓:分久必合合久必分!它们是IP/以太网向SDH发起的全面挑战,现在看来它们是胜利了。

现状:

第一档OTN专线现在多用于政企专网。相比IPRAN和PTN,OTN和互联网物理隔离,资源独享,是现阶段电信最好的端到端专线业务。

第二档是IPRAN云专线,接入端IPRAN,骨干CN2。

第三档是pon专线,直接从olt的pon出纤。

说到这,IP-RAN/PTN战胜了SDH,也就是数通战胜了传统通信,运营商骨干网完全IP化

流媒体视频基础 MSE 入门 & FFmpeg 制作视频预览缩略图和 fmp4

如果检查主流视频网站的视频,就会发现网站的 video 元素的 src 属性都是 blob 开头的字符串。

为什么视频链接前面会有 blob 前缀?这是因为视频网站使用了这篇文章要讲的 MSE 来播放视频。

Media Source Extensions

虽然 video 很强大,但是还有很多功能 video 并不支持,比如直播,即时切换视频清晰度,动态更新音频语言等功能。

MSE(Media Source Extensions)就来解决这些问题,它是 W3C 的一种规范,如今大部分浏览器都支持。

它使用 video 标签加 JS 来实现这些复杂的功能。它将 videosrc 设置为 MediaSource 对象,然后通过 HTTP 请求获取数据,然后传给 MeidaSource 中的 SourceBuffer 来实现视频播放。

如何将 MediaSourcevideo 元素连接呢?这就需要用到 URL.createObjectURL 它会创建一个 DOMString 表示指定的 File 对象或 Blob(二进制大对象) 对象。这个 URL 的生命周期和创建它的窗口中的 document 绑定。

const video = document.querySelector('video')
const mediaSource = new MediaSource()

mediaSource.addEventListener('sourceopen', ({ target }) => {
    URL.revokeObjectURL(video.src)
    const mime = 'video/webm; codecs="vorbis, vp8"'
    const sourceBuffer = target.addSourceBuffer(mime) // target 就是 mediaSource
    fetch('/static/media/flower.webm')
        .then(response => response.arrayBuffer())
        .then(arrayBuffer => {
            sourceBuffer.addEventListener('updateend', () => {
                if (!sourceBuffer.updating && target.readyState === 'open') {
                    target.endOfStream()
                    video.play()
                }
            })
            sourceBuffer.appendBuffer(arrayBuffer)
        })
})

video.src = URL.createObjectURL(mediaSource)
复制代码

addSourceBuffer 方法会根据给定的 MIME 类型创建一个新的 SourceBuffer 对象,然后会将它追加到 MediaSourceSourceBuffers 列表中。

我们需要传入相关具体的编解码器(codecs)字符串,这里第一个是音频(vorbis),第二个是视频(vp8),两个位置也可以互换,知道了具体的编解码器浏览器就无需下载具体数据就知道当前类型是否支持,如果不支持该方法就会抛出 NotSupportedError 错误。更多关于媒体类型 MIME 编解码器可以参考 RFC 4281

这里还在一开始就调用了 revokeObjectURL。这并不会破坏任何对象,可以在 MediaSource 连接到 video 后随时调用。 它允许浏览器在适当的时候进行垃圾回收。

视频并没有直接推送到 MediaSource 中,而是 SourceBuffer,一个 MeidaSource 中有一个或多个 SourceBuffer。每个都与一种内容类型关联,可能是视频、音频、视频和音频等。

视频格式

HTML5 标准指定时,想指定一种视频格式作为标准的一部分,所有浏览器都必须实现。但是对于 H.264 视频编码各个厂商产生的争论,主要是 H.264 非常强大(高画质、高压缩比、成熟的编解码器…),但是它也要高昂的授权费。Mozilla 这类免费浏览器,并没有从其开发的浏览器上获得直接收入,但是让 H.264 加入标准,它就要支付相应的授权费,所有认为是不可接受的。于是后来放弃了视频格式指定的统一,浏览器厂商可以自由选择支持的格式。

不过现在所有主流浏览器都支持 H.264 编码格式的视频,所有选择视频编码时优先选择 H.264 编码。

MediaSource API

MediaSource 属性

属性描述
sourceBuffers返回包含 MediaSource 所有 SourceBufferSourceBufferList 对象
activeSourceBuffers上个属性的子集,返回当前选择的视频轨道,启用的音频规定和显示或隐藏的文本轨道
duration获取或者设置当前媒体展示的时长,负数或 NaN 时抛出 InvalidAccessError
readyState 不为 openSourceBuffer.updating 属性为 true 时抛出 InvalidStateError
readyState表示 MediaSource 的当前状态

readyState 有以下值:

  • closed 未附着到一个 media 元素上
  • open 已附着到一个 media 元素并准备好接收 SourceBuffer 对象
  • ended 已附着到一个 media 元素,但流已被 MediaSource.endOfStream() 结束

MediaSource 方法

方法描述
addSourceBuffer(mime)根据给定 MIME 类型创建一个新的 SourceBuffer 对象,将它追加到 MediaSource 的 SourceBuffers 列表中
removeSourceBuffer(sourceBuffer)移除 MediaSource 中指定的 SourceBuffer。如果不存在则抛出 NotFoundError 异常
endOfStream(endOfStreamError)向 MediaSource 发送结束信号,接收一个 DOMString 参数,表示到达流末尾时将抛出的错误
setLiveSeekableRange(start, end)设置用户可以跳跃的视频范围,参数是以秒单位的 Double 类型,负数等非法参数会抛出异常
clearLiveSeekableRange清除上次设置的 LiveSeekableRange

其中 addSourceBuffer 可能会抛出一下错误:

错误描述
InvalidAccessError提交的 mimeType 是一个空字符串
InvalidStateErrorMediaSource.readyState 的值不等于 open
NotSupportedError当前浏览器不支持的 mimeType
QuotaExceededError浏览器不能再处理 SourceBuffer 对象

endOfStream 的参数可能是如下两种字符串:

  • network 终止播放并发出网络错误信号
  • decode 终止播放并发出解码错误信号

MediaSource.readyState 不等于 open 或有 SourceBuffer.updating 等于 true,调用 endOfStream 会抛出 InvalidStateError 异常。

它还有一个静态方法

静态方法描述
isTypeSupported(mime)是否支持指定的 mime 类型,返回 true 表示可能支持并不能保证

MediaSource 事件

错误描述
sourceopenreadyStateclosedendedopen
sourceendedreadyStateopenended
sourceclosereadyStateopenendedclosed

SourceBuffer API

SourceBuffer 通过 MediaSource 将一块块媒体数据传递给 meida 元素播放。

SourceBuffer 属性

属性描述
mode控制处理媒体片段序列,segments 片段时间戳决定播放顺序,sequence 添加顺序决定播放顺序,
MediaSource.addSourceBuffer() 中设置初始值,如果媒体片段有时间戳设置为 segments,否则 sequence
自己设置时只能从 segments 设置为 sequence,不能反过来。
updating是否正在更新,比如 appendBuffer()remove() 方法还在处理中
buffered返回当前缓冲的 TimeRanges 对象
timestampOffset控制媒体片段的时间戳偏移量,默认是 0
audioTracks返回当前包含的 AudioTrack 的 AudioTrackList 对象
videoTracks返回当前包含的 VideoTrack 的 VideoTrackList 对象
textTracks返回当前包含的 TextTrack 的 TextTrackList 对象
appendWindowStart设置或获取 append window 的开始时间戳
appendWindowEnd设置或获取 append window 的结束时间戳

append window 是一个时间戳范围来过滤 append 的编码帧。在范围内的编码编码帧允许添加到 SourceBuffer,之外的会被过滤。

SourceBuffer 方法

方法描述
appendBuffer(source)添加媒体数据片段(ArrayBufferArrayBufferView)到 SourceBuffer
abort中断当前片段,重置段解析器,可以让 updating 变成 false
remove(start, end)移除指定范围的媒体数据

SourceBuffer 事件

方法描述
updatestartupdatingfalse 变为 true
updateappendremove 已经成功完成,updatingtrue 变为 false
updateendappendremove 已经结束,在 update 之后触发
errorappend 时发生了错误,updatingtrue 变为 false
abortappendremoveabort() 方法中断,updatingtrue 变为 false

注意事项

  1. 几乎所有 MediaSource 方法调用或设置属性,在 MediaSource.readyState 不是 open 时会抛出 InvalidStateError 错误,应该在调用方法或设置属性前查看当前状态,即使是在事件回调中,因为可能在回调执行之前改变了状态。此外 endOfStream 方法还会因为 SourceBufferupdatingtrue 时也抛出该异常
  2. 在调用 SourceBuffer 方法或设置属性时,应该检查 SourceBuffer 是否为 false
  3. MediaSource.readyState 的值是 ended 时,调用 appendBuffer()remove() 或设置 modetimestampOffset 时,将会让 readyState 变为 open,并触发 sourceopen 事件,所有应该要有处理多个 sourceopen 事件准备

Initialization Segment

如果随便找一个 mp4 文件,使用上面那个例子播放,就会发现播放不了。这是因为 SourceBuffer 接收两种类型的数据:

  1. Initialization Segment 视频的初始片段,其中包含媒体片段序列进行解码所需的所有初始化信息。
  2. Media Segment 包含一部分媒体时间轴的打包和带时间戳的媒体数据。

MSE 需要使用 fmp4 (fragmented MP4) 格式,MP4 文件使用面向对象格式其中包含 Boxes (或叫 Atoms),可以使用 这个网站 查看 Mp4 文件信息。

这是一个普通的 MP4 文件,可以看到它有一个很大的 mdat (电影数据)box

这是 fragmented MP4 的截图,ISO BMFF 初始化段定义为单个文件类型框(File Type Box ftyp)后跟单个电影标题框(Movie Header Box moov),更多信息可以查看 ISO BMFF Byte Stream Format

moov 只包含一些视频基础的信息(类型,编码器等),moof 存放样本位置和大小,moof 框后都有一个 mdat,其中包含如前面的 moof 框中所述的样本。

要查看当前视频是不是 fmp4,就可以看 ftyp 后面是不是跟着 moov,然后是 moof mdat 对就行了。

要将普通 MP4 转换成 FMP4 可以下载 Bento4。然后用命令行输入如下命令就可以了。

mp4fragment ./friday.mp4 ./friday.fmp4
复制代码

Bento4 /bin 目录中有非常多好用的 mp4 工具,/utils 目录中都是 python 实用脚本。

工具

除了上面介绍的 Bento4,还有很多其他好用的工具。有了下面的工具,就可以快速制作 MSE 实践的视频素材了。

chrome://media-internals/

chrome://media-internals/ 是 chrome 浏览器用来调试多媒体的工具,直接在地址栏输入该值就可以。

Shaka Packager

Shaka Packager 是 Google 出的一个小巧视频工具,它只有 5M 左右,它可以用来查看视频信息,分离音频和视频,还支持 HLS 和 DASH。

它的命令行使用格式如下:

packager stream_descriptor[,stream_descriptor] [flags]
复制代码

比如用它查看视频信息:

packager in=friday.mp4 --dump_stream_info # 查看视频信息

File "friday.mp4":
Found 2 stream(s).
Stream [0] type: Audio
 codec_string: mp4a.40.2 # 音频的编码器字符串
 time_scale: 44100
 duration: 271360 (6.2 seconds)
 is_encrypted: false
 codec: AAC
 sample_bits: 16
 num_channels: 2
 sampling_frequency: 44100
 language: und

Stream [1] type: Video
 codec_string: avc1.4d401e # 视频的编码器字符串
 time_scale: 3000
 duration: 18500 (6.2 seconds)
 is_encrypted: false
 codec: H264
 width: 640
 height: 480
 pixel_aspect_ratio: 1:1
 trick_play_factor: 0
 nalu_length_size: 4
复制代码

更多请查看官方文档

FFmpeg

FFmpeg 是功能非常强大的视频处理开源软件,很多视频播放器就是使用它来做为内核。后面文章的实例都会使用这个工具。

比如上面将普通 MP4 转换为 FMP4,可以使用如下命令:

ffmpeg -i ./friday.mp4 -movflags empty_moov+frag_keyframe+default_base_moof f.mp4
复制代码

它的命令行格式如下:

ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...
复制代码

它可以有不限数量的输入和输出文件,-i 后面是输入 url,后面不能解析为参数的为输出文件。

 _______              ______________
|       |            |              |
| input |  demuxer   | encoded data |   decoder
| file  | ---------> | packets      | -----+
|_______|            |______________|      |
                                           v
                                       _________
                                      |         |
                                      | decoded |
                                      | frames  |
                                      |_________|
 ________             ______________       |
|        |           |              |      |
| output | <-------- | encoded data | <----+
| file   |   muxer   | packets      |   encoder
|________|           |______________|
复制代码

它将输入文件的容器解开,然后对里面的数据进行解码,然后按照指定的格式进行编码,然后使用指定的容器进行封装生成输出文件。在 decoded frames 后 FFmpeg 可以使用 filter 进行处理,比如添加滤镜、旋转、锐化等操作,filter 分为简单和复杂,复杂可以处理多个输入流。

如果我们只是想改变视频的容器,那么就可以省略解码和编码过程,来提升速度。

ffmpeg -i input.avi -c copy output.mp4
复制代码

-c 是指定编码器,-c copy 表示直接复制编码,-c:v 表示视频编码,-c:a 表示音频编码,比如 -c:v libx264 表示使用 CPU 将视频编码为 h.264-c:v h264_nvenc 则是使用 N卡,这样速度更快。-c:a copy 表示直接复制音频编码。

ffmpeg -y -i myvideo.mp4 -c:v copy -an myvideo_video.mp4
ffmpeg -y -i myvideo.mp4 -c:a copy -vn myvideo_audio.m4a
复制代码

-an 去除音频流 -vn 去除视频流。-y 是不经过确认,输出时直接覆盖同名文件。

ffmpeg -help #查看帮助
ffmpeg -i input.mp4 # 查看视频信息
ffmpeg -formats # 查看支持的容器
ffmpeg -codecs # 查看支持的编码格式
ffmpeg -encoders # 查看内置的编码器
复制代码

更多关于 FFmpeg 请查看官方文档

视频缩略图预览

了解了上面好用的工具,就来用 FFmpeg 来实现一个视频播放器小功能吧。

现在视频网站,当鼠标放到进度条上时就会出现,一个小缩略图来预览这个时间点内容。

ffmpeg -i ./test.webm -vf 'fps=1/10:round=zero:start_time=-9,scale=160x90,tile=5x5' M%d.jpg
复制代码

我们可以通过上面这个命令生成一个雪碧图,由 25 张 160×90 预览图组成。

-vf 参数后面跟着过滤器,多个过滤器用 , 分开,一个过滤器多个参数使用 : 分开。

fps=1/10 表示 10 秒输出一张图,fps=1/60 为一分钟一张,round=zero 时间戳向 0 取整,start_time=-9 是因为 fps 是每多少秒生成一张,并不是从 0 秒开始 -9 是让它从 1 秒开始截取,忽略掉 0 秒的黑屏帧。

scale=160x90 设置输出图像分辨率大小,tile=5x5 将小图用 5×5 的方式组合在一起,M%d.jpg 表示输出为 jpg,而且文件是 M1.jpg M2.jpg... 这样递增。

如果想用 NodeJS,可以用 node-fluent-ffmpeg 的 thumbnails 方法来生成。

有了雪碧图,我们就在上篇文章实现的播放器的基础上在加个视频缩略图功能。主要通过 css 的 background 来实现。

const thumb = document.querySelector('.thumb')
const gapSec = 10 // 一张图片显示几秒
const images = [...] // 图片
const row = 5, col = 5 // 一张图有几行几列
const width = 160, height = 90; // 缩略图的宽高
const thumbQuantityPerImg = col * row

function updateThumbnail(seconds) { // 传入要显示缩略图的秒数
    const thumbNum = (seconds / gapSec) | 0; // 当前是第几张缩略图
    const url = images[(thumbNum / thumbQuantityPerImg) | 0];
    const x = (thumbNum % col) * width; // x 的偏移量
    const y = ~~((thumbNum % thumbQuantityPerImg) / row) * height; // y 的偏移量

    thumb.style.backgroundImage = `url(${url})`;
    thumb.style.backgroundPosition = `-${x}px -${y}px`;
}
复制代码

效果如下

image.png

这里只展示缩略图更新的逻辑,忽略了时间显示,事件处理和样式等代码。

完整的代码请参考 NPlayer

在线演示:nplayer.js.org/

视频切片

有了 MSE 我们就可以将一个视频分割成多个小视频,然后可以自己控制缓存进度来节省流量,还可以将视频压缩成不同的分辨率,在用户网不好的情况动态加载码率低的分段,分成多段还可以实现插入广告,动态切换音频语言等功能。

./audio/
  ├── ./128kbps/
  |     ├── segment0.mp4
  |     ├── segment1.mp4
  |     └── segment2.mp4
  └── ./320kbps/
        ├── segment0.mp4
        ├── segment1.mp4
        └── segment2.mp4
./video/
  ├── ./240p/
  |     ├── segment0.mp4
  |     ├── segment1.mp4
  |     └── segment2.mp4
  └── ./720p/
        ├── segment0.mp4
        ├── segment1.mp4
        └── segment2.mp4
复制代码

当然也可以不切割视频,而使用 HTTP Range 来范围请求数据。

ffmpeg -i ./friday.mp4 -f segment -segment_time 2 -segment_format_options movflags=dash ff%04d.mp4
复制代码

我们使用上面命令将一个视频切成 2 秒的 fmp4 视频片段。

window.MediaSource = window.MediaSource || window.WebKitMediaSource
const video = document.querySelector('video')
const mime = 'video/mp4; codecs="avc1.4d401e, mp4a.40.2"'
const segments = [
    '/static/media/ff0000.5b66d30e.mp4', 
    '/static/media/ff0001.89895c46.mp4', 
    '/static/media/ff0002.44cfe1e4.mp4'
]
const segmentData = []
let currentSegment = 0

if ('MediaSource' in window && MediaSource.isTypeSupported(mime)) {
    const ms = new MediaSource()
    video.src = URL.createObjectURL(ms)
    mediaSource.addEventListener('sourceopen', sourceOpen)
}

function sourceOpen({ target }) {
    URL.revokeObjectURL(video.src)
    target.removeEventListener('sourceopen', sourceOpen)
    const sb = target.addSourceBuffer(mime)
    fetchSegment(ab => sb.appendBuffer(ab))
    sb.addEventListener('updateend', () => {
        if (!sb.updating && segmentData.length ) {
            sb.appendBuffer(segmentData.shift())
        }
    })
    video.addEventListener('timeupdate', function timeupdate() {
        if (
            currentSegment > segments.length &&
            !sb.updating &&
            target.readyState === 'open'
        ) { // 所有片段全部加载完成
            target.endOfStream()
            video.removeEventListener('timeupdate', timeupdate)
        } else if (
            video.currentTime > (currentSegment * 2 * 0.8) 
            // 一个片段时长 2 秒
            // 在一个片段播放到 80% 的时候才去请求下一个片段
        ) {
            fetchSegment(ab => {
                if (sb.updating) {
                    segmentData.push(ab)
                } else {
                    sb.appendBuffer(ab)
                }
            })
        }
    })
    video.addEventListener('canplay', () => {
        video.play()
    })
}

function fetchSegment(cb) {
    fetch(segments[currentSegment])
        .then(response => response.arrayBuffer())
        .then(arrayBuffer => {
            currentSegment += 1
            cb(arrayBuffer)
        })
}
复制代码

这个实例非常简单,并没有处理 seek,自适应码率等复杂功能。

总结

现在视频网站几乎全部都在使用 MSE 来播放视频。使用 MSE 有提供更好的用户体验,更加节约成本等好处。虽然视频播放一般使用 hls dash 等协议的开源客户端来播放视频,我们自己不会使用到 MSE,但这些客户端底层都是使用 MSE,了解 MSE 才更了解这些客户端。

这是弹幕播放器系列文章第二篇,上篇请查看 从零开发弹幕视频播放器1。下篇请查看 原来爱优腾等视频网站都是用这个来播放流媒体的

作者:wopen
链接:https://juejin.cn/post/6953777965838630926

正向代理、反向代理、透明代理

代理:它位于用户客户端和服务端之间,顾名思义它是一个中间商,代替客户端向服务端请求资源,并把服务端响应的资源传递给客户端而不用客户端自己去访问。

根据所处位置的不同分为正向代理、反向代理

一、正向代理:

  正向代理主要是代替客户端请求和接受互联网资源,比如当我们直接访问youtube的时候,你知道结果肯定是要扑街的,这时候可以去找个代理,通过代理我们就能访问了。这时候我们把代理服务看着是客户端。

  正向代理的用途 :

(1)访问本地无法访问的服务器;

(2)缓存作用,加速访问资源;

(3)客户端访问授权,进行上网认证;

(4)代理会记录访问记录,对外可以隐藏访问者的行踪。

 二、反向代理:

   它位于服务端,主要是将用户的web请求分发到后面的真实服务器上(负载均衡),并将真实服务器返回的结果交给用户,此时代理服务器位于服务端,对外就表现为一个服务器。

图2反向代理

反向代理的作用:

(1)保证服务端内部网路的安全,大型网站,通常将反向代理作为公网访问地址,Web服务器是内网。

(2)负载均衡,通过反向代理服务器来优化网站的负载,降低服务器压力。

三、透明代理:

   透明代理的意思是客户端根本不需要知道代理服务器的存在,它改变你的request fields(报文),并会传送真实IP,多用于路由器的NAT转发中。

透明代理也称为强制代理、内联代理或拦截代理。与常规代理相反,透明代理不需要对现有设置进行任何更改。它在浏览互联网时在用户不知情的情况下实施。

透明代理不会操纵请求或更改您的IP。它执行重定向并可用于身份验证。透明代理充当由ISP实现的缓存代理。

透明代理常用于缓存某些高带宽资源。例如,视频将被缓存到代理服务器,当请求查看该视频时,将呈现缓存副本而不向目标服务器发送请求。这是为了有效地使用带宽并缩短响应时间。

组织使用透明代理来限制对特定网站的访问。这用于审查内容。透明代理可以根据特定规则阻止对Internet的访问。此类代理服务器也用于防止DDoS攻击。它可以防止此类冗余请求泛滥并关闭实际的目标服务器。

可以在路由器刷OpenWRT安装shadowsocks使用透明代理+去DNS污染

拥有透明代理的缺点之一是用户控制权被透明代理带走,而用户没有意识到这一点。这是一个隐私问题。其次,透明代理不健壮,容易受到中间人攻击。网络流量可以重新路由到不同的代理。

透明代理的主要问题是可以监控您的所有流量。ISP 可以完全跟踪您的网络跟踪。如果您不希望被监控,第一步是检测您是否使用透明代理。一种常见的方法是在 Chrome 地址栏中输入无效的 URL。未找到网站时来自 Chrome 的典型错误消息与使用透明代理连接时显示的错误消息不同。

如果您正在浏览HTTPS网站,您可以通过单击浏览器上的锁定符号来查看SSL证书信息。当它来自 ISP 时,此证书看起来会有所不同。

Nmap 等命令也用于检测透明代理。