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


  • 2017-01-19 00:45:56

    nodejs之process进程

    虽然node对操作系统做了很多抽象的工作,但是你还是可以直接和他交互,比如和系统中已经存在的进程进行交互,创建工作子进程。node是一个用于事件循环的线程,但是你可以在这个事件循环之外创建其他的进程(线程)参与工作。

  • 2017-01-19 01:05:32

    process对象

    process对象是Node的一个全局对象,提供当前Node进程的信息。它可以在脚本的任意位置使用,不必通过require命令加载。该对象部署了EventEmitter接口。

  • 2017-01-20 21:59:11

    WEBPACK DEV SERVER

    webpack-dev-server是一个小型的node.js Express服务器,它使用webpack-dev-middleware中间件来为通过webpack打包生成的资源文件提供Web服务。它还有一个通过Socket.IO连接着webpack-dev-server服务器的小型运行时程序。

  • 2017-01-21 10:32:29

    Vue-cli proxyTable 解决开发环境的跨域问题

    和后端联调时总是会面对恼人的跨域问题,最近基于Vue开发项目时也遇到了这个问题,两边各自想了一堆办法,查了一堆资料,加了一堆参数,最后还得我把自己的localhost映射成上线时将要使用的域名。

  • 2017-01-21 21:44:29

    详解 ESLint 规则,规范你的代码

    在很久之前就想通过工具来规范自己的代码风格,减少程序出错的概率,如果看过我的 一个前端程序猿的Sublime Text3的自我修养 ,这篇博客的朋友,肯定知道在当时我使用 SublimeLinter-jshint 插件来规范风格,但是实际上一直懒癌发作也没去看它的文档,使用着它默认的规则。不过现在是时候切换到 ESLint 了!

  • 2017-01-23 23:09:16

    使用 CSS3 实现超炫的 Loading(加载)动画效果

     SpinKit 是一套网页动画效果,包含8种基于 CSS3 实现的很炫的加载动画。借助 CSS3 Animation 的强大功能来创建平滑,易于定制的动画。SpinKit 的目标不是提供一个每个浏览器都兼容的解决方案,而是给现代浏览器提供更优的技术实现方案和更佳的使用体验。(为保证最佳的效果,请在 Chrome、Firefox 和 Safari 等现代浏览器中浏览)