H5流式播放(FMP4转封装与mediaSource)

2020-12-17 11:00:37

参考地址 H5流式播放(FMP4转封装与mediaSource)

mediaSource接口的介绍

W3C上有明确关于mediaSource 扩展接口的文档。mediaSource 扩展文档中是这么定义的, 它允许JS脚本动态构建媒体流用于和,允许JS传送媒体块到H5媒体元素。这种接口的应用可以让h5播放器实现持续添加数据进行播放。做as的朋友都知道as中的appendBytes方法,一种添加二进制数据进行播放的方式。这两种接口在概念上是类似的。只是里面的定义和对媒体文件的要求有所不同。对于mediaSource扩展接口我只介绍我们主要应用的几个。

判断是否存在mediaSource扩展类

该语句决定了整个播放方式是否可以使用meidaSource接口控制播放。

window.MediaSource = window.MediaSource || window.WebKitMediaSource;//分为pc和移动两种1

isTypeSupported

判断是否支持要解码播放的视频文件编码和类型。

MediaSource.isTypeSupported('video/webm; codecs=“vorbis,vp8”’);//是否支持webmMediaSource.isTypeSupported('video/mp4; codecs=“avc1.42E01E,mp4a.40.2”’);//是否支持mp4MediaSource.isTypeSupported('video/mp2t; codecs=“avc1.42E01E,mp4a.40.2”’);//是否支持ts123

addSourceBuffer

该事件是在触发sourceopen监听时进行的,该动作会创建一个sourceBuffer对象用于数据流的播放处理。如果mediaSource对象无法触发该事件,则无法通过该扩展进行播放的。

sourceBuffer = mediaSource.addSourceBuffer(DOM string);
如:
mediaSource.addSourceBuffer('video/mp4; codecs=“avc1.42E01E,mp4a.40.2”’);123

appendBuffer

sourceBuffer对象的方法,用于持续数据的添加播放。

sourceBuffer.appendBuffer(Uint8array);// Uint8array:媒体二进制数据1

buffered

类型为TimeRanges,描述了添加进去的所有媒体数据的range信息。为一个数组,里面标示了持续或间断的时间信息列表。如:

for(var i=0;i<buffered.length;i++){
    start = buffered.start(i);//第i个range信息的开始时间
    end = buffered.end(i);//第i个range信息的结束时间
}12345

如果播放的媒体数据是连续的则只有一个开始时间点和一个结束时间点。所以如果要计算缓冲中还存在多少时间则可以通过该描信息与当前播放时间点进行换算。

/**
 * Created by YL on 2016/5/21.
 */function play() {    if (!this.mediaSource) {        this.mediaSource = new MediaSource();        var me = this;        this.mediaSource.addEventListener('sourceopen', function () {
            me.onMediaSourceOpen();
        });        this.mediaSource.addEventListener('sourceended', function () {
            me.onMediaSourceEnded();
        });        this.mediaSource.addEventListener('sourceclose', function () {
            me.onMediaSourceClosed();
        });        this.mediaSource.addEventListener('error', function () {
            me.onUpdateError();
        });        this.video = this.createNewVideo();//创建HTMLMediaElement;        this.video.src = window.URL.createObjectURL(this.mediaSource);        this.video.play();
    }    if (!this.sourceBuffer) {        return;
    }    if (this.sourceBuffer.updating)//上一块数据还在添加中
    {        return;
    }    try {        this.sourceBuffer.appendBuffer(dataBytes);//添加数据
    }    catch (err) {
    }
}function createNewVideo() {    var newVideo = document.createElement("video");
    newVideo.id = "player";
    newVideo.width = this.videoWidth;
    newVideo.height = this.videoHeight;    return newVideo;
}//事件侦听onMediaSourceOpen: function () {//DOMString 可以通过转码获得;    var typeName = 'video/mp2t; codecs=“avc1.64001f”’;
    var issurpport = MediaSource.isTypeSupported(typeName);//是否支持
    this.mediaSource.duration = this.totalDuration;//设置视频总时长
    this.sourceBuffer = this.mediaSource.addSourceBuffer(typeName);

}

onMediaSourceEnded:function () {
    console.log("source ended!");
}

onMediaSourceClosed:function () {
    console.log("source closed!");
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172

FMP4封装格式要求

在W3C非官方标准文档ISO BMFF Byte Stream Format中明确规定了fmp4媒体文件封装的具体要求。
fmp4的封装可以分为Initialization Segments和media Segments两种,这两种的基本封装格式相差不多。

关于Initialization Segments必须注意的以下几点:
  • 文件类型声明ftyp的box包含的major_brand或者compatible_brand用户代理方必须支持。

  • mohd box里的任意box或者字段不能违背在ftyp box里定义的major_brand或者compatible_brands中的任何一个授权。

  • mohd中包含的samples的track(如stts,stsc,或者stco 中的entry_count必须被设置成0);

  • mvex box必须在moov中被包含,注:该box说明该视频包含moof需要解析。

media Segments的封装要求基本类似,同时增加了以下几点:
  • styp必须遵循ftyp;

  • traf里面必须包含tfdt;

  • mdat里的samples必须和trun里面的对应。

不同浏览器的解析兼容有所不同,未按上述要求封装的fmp4有的浏览器也可解析,不过数据的播放会存在问题,如卡顿,不流畅等。

FMP4封装主要box说明



  1. BOX:这个box由长度信息和box类型两部分组成,即size,type。size为一个长度数据,描述type里面包含的数据长度。

  2. fullbox:继承BOX包含 version(8bit)和flags(24bit)两个数据,版本和格式描述;

  3. ftyp,styp:继承BOX,包含3个描述字段:major_brand,minor_version,compatible_brands.
    如:0000001C 66747970 6D703432 00000001 69736F6D 6D703432 64617368


其中
0000001C为size长度的16进制32位数据,计算结果为28,即该box的总字节长度;第二个4个字节66747970为ftyp的code码的16进制数;第三个4字节数6D703432为major_brand字段值,转化成字符串为mp42;第四个4字节数00000001为版本标示;第五,六,七分别描述了一个list数组(69736F6D6D703432 64617368)为compatible_brands转化为字符串为isommp42dash。

  • moov:继承BOX,包含mvhd,mvex,trak,这里有几点需要注意。mvex必须包含,trak下面的stts,stsc,stco里面的entry_count设置成0.

  • mvex:包含:mehd,trex。

  • trex:继承fullbox,包含字段:

    unsigned int(32) version;//0 unsigned int(32) track_ID;//1.视频,2.音频
    unsigned int(32) default_sample_description_index;//sample描述索引
    unsigned int(32) default_sample_duration; //默认sample时长 unsigned
    int(32) default_sample_size;//默认sample大小 unsigned int(32)
    default_sample_flags//默认sample标示。 以上default_的数据会在moof中没有声明时默认使用。

  • trak:继承BOX,包含的box有tkhd,mdia。

  • tkhd:继承fullbox,包含字段:

    unsigned int(32) version;//版本,这里默认写成0
    unsigned int(32) creation_time;//创建时间
    unsigned int(32) modification_time;//修改时间
    unsigned int(32) track_ID;//1:视频,2:音频
    const unsigned int(32) reserved = 0;
    unsigned int(32) duration;//时长,因为要封装成fmp4,这里写为0;
    const unsigned int(32)[2] reserved = 0;
    template int(16) layer = 0;
    template int(16) alternate_group = 0;
    template int(16) volume = {if track_is_audio 0x0100 else 0};
    const unsigned int(16) reserved = 0;
    template int(32)[9] matrix={ 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 };
    // unity matrix
    unsigned int(32) width;
    unsigned int(32) height;

  • mdia:继承box,包含mdhd,hdlr,minf;

  • mdhd:继承fullbox,包含字段:

    unsigned int(32) creation_time;//创建时间 unsigned int(32)
    modification_time;//修改时间 unsigned int(32) timescale;//时间比例 unsigned
    int(32) duration;//时长,fmp4可以写为0 bit(1) pad = 0; unsigned int(5)[3]
    language; //写为:0x55C4 code unsigned int(16) pre_defined = 0;

  • moof:继承BOX,包含mfhd,traf;

  • mfhd:继承Fullbox,包含字段:

    unsigned int(32) version and flags; unsigned int(32)
    sequence_number;//fragment序列号


  • 13.traf:包含tfhd,trun,tfdt;
    14.tfhd:继承fullbox,包含字段:
    unsigned int(32) version and flags;
    unsigned int(32) track_ID;
    unsigned int(64) base_data_offset;
    unsigned int(32) sample_description_index;
    unsigned int(32) default_sample_duration;
    unsigned int(32) default_sample_size;
    unsigned int(32) default_sample_flags

    字段1版本和标识符值描述:
    * 0x000001 base-data-offset-present//基本数据偏移位置是否出现
    * 0x000002 sample-description-index-present//samplem描述索引是否出现
    * 0x000008 default-sample-duration-present//默认sample时长是否出现
    * 0x000010 default-sample-size-present//默认sample大小是否出现
    * 0x000020 default-sample-flags-present//默认sample标志是否出现
    * 0x010000 duration-is-empty 时长为空(default_sample_duration或者trex里的时长)
    0x020000 default-base-is-moof//从每个moof的开始计算偏移

    在这里我们使用0x020000,即每个视频的偏移位置从moof算起。

    15.trdt:继承Fullbox,包含字段:

    unsigned int(32) version //使用0;
    if (version==1) {
    unsigned int(64) baseMediaDecodeTime;
    } else { // version==0
    unsigned int(32) baseMediaDecodeTime;
    }//所有添加进去的sample解码时长总和。

    16.trun:继承fullbox,这是一个主要字段,描述了各个sample 信息。
    字段包括:
    unsigned int(32) version and flags;//描述标示
    unsigned int(32) sample_count;//sample数量。
    signed int(32) data_offset;//sample开始偏移
    unsigned int(32) first_sample_flags;//关键标示符
    {
    unsigned int(32) sample_duration;//sample时长
    unsigned int(32) sample_size;//sample大小
    unsigned int(32) sample_flags//sample标示
    if (version == 0)//视频播放时间与解码时间偏移
    { unsigned int(32) sample_composition_time_offset; }
    else
    { signed int(32) sample_composition_time_offset; }
    }[ sample_count ]

    字段1说明:
    * 0x000001:data-offset-present//数据偏移,从moof算起,到 mdat结束。
    * 0x000004:first-sample-flags-present;//关键sample标示是否描述。
    * 0x000100 sample-duration-present: 标示每一个sample有他自己的时长, 否则使用默认值.
    * 0x000200 sample-size-present:每个sample拥有大小,否则使用默认值
    * 0x000400 sample-flags-present:每个sample有他自己的标志,否则使用默认值
    * 0x000800 sample-composition-time-offsets-present; 每个sample 有一个composition time offset
    这里我们根据视频和音频分别采用不同值,标示符的值是有各个要使用的标示值相加获得。
    17.mdat:继承Box,视频和音频的主体数据。
    字段:bit(8) data[];
    以上是一些基本box的描述,具体细节可以参考INTERNATIONAL STANDARD ISO/IEC 14496-12 里面的详细定义.
    6.FMP4封装中的问题和解决方式
    在fmp4转封装的过程中最基本要注意的是box的描述尽量完整,每个代理方对box的兼容性不同,有的在缺失部分描述信息时依然可以完成播放,而有的则会直接出现解码错误退出播放。
    我们这里主要注意几点:
    1.initsegment 描述ftyp开头,包含moov,moof,mdat;
    2.mediasegment 描述styp开头,包含moov,moof,mdat;有的浏览器,如safari8.0在没有moov的情况下依然可以流畅的播放视频。但是在chorome浏览器下则会出现解码错误。
    3.tfhd:tf_flags 这里使用0x020000,只说明如果base data offset为0时使用trun里面相对于moof的数据偏移信息。该标示在ios5以后的版本中支持。因为我们需要转封装成fmp4,所以应该使用相对于moof的偏移,这样每次append进去的数据才不会在计算起始位置上出现不准确的问题。
    4.tfdt box的信息中baseMediaDecodeTime会决定mediaSource里的TimeRange信息的起始。这个值有3种途径获得,1.计算前面添加数据的总时长;2.提取要添加数据的第一个sample时间戳。3.根据ts数据的id信息。
    5.trun box的描述:version和flags描述值根据自己的需要计算获得,比如视频数据我们需要描述实际数据偏移(data offset),关键帧标示(first sample flags)以及每个sample标示符,大小和时间偏移,而这些数据对应的开始标示分别为:0x000001(data offset),0x000004(first sample flags),0x000200(sample size present),0x000400(sample flags present),0x000800(sample-composition-time-offsets-present).把这些值相加为0xe05(version flags).所以说version flags的值决定了下面描述信息的分析方式。而对于音频数据我们使用0x201,描述信息中只包含数据偏移值和每个sample的大小。

    原文链接:http://chenzhaofei.lofter.com/post/1d03c1b7_58d8a79
    mediaSource接口参考:http://www.w3.org/TR/media-source/#sourcebuffer-input-buffer
    ISO BMFF 格式要求参考:http://www.w3.org/2013/12/byte-stream-format-registry/isobmff-byte-stream-format.html


  • 2019-08-06 15:30:08

    小程序展示富文本

    然而rich-text有个问题,它不能够给内联img设置宽度100%,这样图片就容易溢出屏幕。我们只能在后台返回富文本的时候对图片的img标签进行格式化。或者前端进行格式化也行,我赞成使用后端,因为很多种情况我们不一定都能想得到。

  • 2019-08-07 09:07:32

    最全的Service Worker讲解

    Service Worker 最主要的特点是:在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和截拦作用域范围内所有页面的 HTTP 请求。 基于 Service Worker API 的特性,结合 Fetch API、Cache API、Push API、postMessage API 和 Notification API,可以在基于浏览器的 web 应用中实现如离线缓存、消息推送、静默更新等 native 应用常见的功能,以给 web 应用提供更好更丰富的使用体验。

  • 2019-08-07 09:09:19

    windows系统下定时关闭程序

    其中xxx.exe是你要关闭的进程中运行的exe,可以ctrl+alt+del打开任务管理器,进到详细信息查看 然后把.txt文件后缀改成.bat(此时要在查看一栏勾上文件拓展名,要不还是txt文档)

  • 2019-08-07 09:16:43

    一个比较完美的PWA例子

    但就目前来讲,PWA是Google主推的一项技术标准,FireFox,Chrome以及一些基于Blink的浏览器已经支持渐进式Web应用了,Edge上对渐进式Web应用的支持还在开发。Apple公司也表示会考虑在自己Safari支持PWA。然而这项功能已经进入了WebKit内核的五年计划中。长期来看,对浏览器兼容性的支持方面应该已经不算太大问题了。况且在现阶段,在不支持渐进式Web应用的浏览器中,你的应用也只是无法使用渐进式Web应用的离线功能而已,除此之外的功能均可以正常使用。

  • 2019-08-07 09:57:48

    spring data jpa 实体类中字段不与数据库表映射

    当我们使用spring data jpa开发的时候,会将实体类中的成员变量与表中的字段一一对应,当我们在实体类中加上一个不与数据库表一一对应的成员变量的时候,此时我们只要在这个成员变量上加上注解@Transient @

  • 2019-08-07 17:16:53

    如何在 Node.js 中使用 import / export 的三种方法

    因为一些历史原因,虽然 Node.js 已经实现了 99% 的 ES6 新特性,不过截止 2018.8.10,How To Enable ES6 Imports in Node.JS 仍然是老大难问题,下面我来介绍三种方法可以让我们在 Node.js 中使用 import/export 。

  • 2019-08-13 08:56:46

    nuxtjs组合element

    添加elementUI 插件,plugins->ele.js,代码如下