zoukankan      html  css  js  c++  java
  • 使用canvas检测HTML5视频解码错误

      乍一看这标题,有点吊炸天的赶脚,canvas跟<video>能有什么联系?不过请放心我不是标题党。事情是这样的:

      HTML5的<video>标签所支持的视频格式确实有限,mp4文件必须是H264编码的才行,若不是H264编码,在chrome下会只有声音没有画面,在FireFox下直接连声音也没有,而且控制台会显示警告:

      因为浏览器使用的解码器也是H264的。如果用户在本地可以观看的mp4视频上传后却无法正常播放,这种体验是相当糟糕的。

      我尝试在文件上传阶段进行检测,然而HTML5的FILE API能力也是有限的,只能获取文件名称、大小、MIME类型,对于视频的编码却无法检测到。

      既然无法从上传阶段阻止用户,那么退一步讲,在视频无法播放的时候,我们希望可以检测到,并且给用户一个提示“视频解码错误”,这样他就不会有疑惑“我的视频为什么无法播放呢?”。

      首先想到的是video的API,video有onerror事件,但是此事件只能在src地址错误或其他原因加载不到视频资源时触发,当加载到视频发生解码错误时,并不会触发。略蛋疼。这么看来按照标准的东西是无法检测到了,所以必须另辟蹊径了。答案就是:

      canvas读取图片像素点的能力

      前些天看了前端手记的这篇文章印象颇深,http://www.cssha.com/video2txt-canvas。利用canvas读取图片像素点,进而转化为文本图片。更厉害的是canvas的drawImage方法还可以传入视频,获取到视频某一帧的像素点。于是一个想法在脑中萦绕,解码错误的视频是没有画面的黑屏,我可以用canvas绘制视频,根据所绘制的内容来判断画面是不是在动,遂想到如下思路:

    1. 在视频开始播放时,每隔一定时间用canvas绘制一次视频画面
    2. 对每次canvas绘制的图片进行像素点采样,存入数组
    3. 扫描几次后,比较每次采样的像素点rgb值是否相同,即检测画面是否变化了
    4. 根据画面是否在“运动”来检测是否解码成功了

      这种办法当然也有局限,下面是几个注意事项:

    1. canvas绘制视频画面的次数控制。绘制图片并采样获取像素点是消耗性能的,所以这个扫描过程不应该伴随视频播放的整个时间段。只需在开始播放的几秒内进行检测即可。
    2. 若恰巧有某个视频,开始的几秒内就是一个静止的画面,那检测就出错了。

      局限归局限,先把想法写成代码试试,于是有如下代码:

    //比较两个长度相等类型相同的数组是否相等
    function arrayEq(a1,a2){
        for(var i=0,len=a1.length;i<len;i++){
            if(a1[i]!=a2[i]){
                return false;
            }
        }
        return true;
    }
    //比较采样得到的数组是否相等
    function arrayAllEq(array){
        for(var i=0,len=array.length;i<len;i++){
            if(!arrayEq(array[i],array[i+1])){
                return false;
            }
        }
        return true;
    }
    
    
    var can = $('<canvas id="canvas" style="display:none"></canvas>').appendTo('body');
    var canvas = can.get(0);
    var width = $('#vi').width();
    var height = $('#vi').height();
    var ctxt = canvas.getContext('2d');
    var media = document.getElementById('vi');
    var resultArray = [];
    var stopScan = false;
    var scanImg = function(){
        if(stopScan)return false;
        try{
            ctxt.drawImage(media, 0, 0,width,height);    
            var data = ctxt.getImageData(0, 0, width,height).data;
            var array = [];
            for(var i =0,len = data.length; i<len;i+=4*100){
                var red = data[i],
                    green = data[i+1],
                    blue = data[i+2],
                    alpha = data[i+3];
                array.push(red,green,blue,alpha);
            }
            resultArray.push(array);
            return true;
        }
        catch(e){
            alert('视频解码错误,请使用H264编码的mp4文件!');
            stopScan = true;
        }
    }
    $('video').on('play',function(){
        //每隔一定时间扫描一次画面
        scanImg();
        setTimeout(scanImg,1000);
        setTimeout(scanImg,2000);
        setTimeout(scanImg,3000);
        setTimeout(scanImg,5000);
        setTimeout(function(){
            if(scanImg()){
                if(arrayAllEq(resultArray)){
                    alert('视频解码错误,请使用H264编码的mp4文件!');
                }
                else{
                    alert('监测结束,视频正常');
                }    
            }
        },8000);
    });
    $('video').on('pause',function(){
        stopScan = true;
    });
    View Code

      当我怀着激动的心情开始测试时,发现事实真不是想象的那样。Chrome下,当一个视频无法解码时,drawImage方法直接无法执行,会报错。完了,美好的想法泡汤了。。。

      不过转而一想,视频解码错误,drawImage方法就报错,如果写在try catch语句中,不就可以捕捉到了吗?看来还没到死路,这样连像素点采样都省了,可以直接检测到了。于是乎代码就简化成了下面这样:

    //检测视频是否解码错误
    function checkVideoParseError(){
        var videos = $('video');
        if(videos.length>0){
            var can = $('<canvas id="canvas" style="display:none"></canvas>').appendTo('body');
            var canvas = can.get(0);
            var ctxt = canvas.getContext('2d');
            var scanImg = function(video){
                try{
                    ctxt.drawImage(video, 0, 0);    
                }
                catch(e){
                    alert('视频解码错误,请使用H264编码的mp4文件!');
                }
            }
    
            videos.on('play',function(){
                var _this = this;
                scanImg(_this);
                setTimeout(function(){scanImg(_this);},1000);
            })
        }
    }

      在1秒后的绘制图片动作会捕捉到异常。没想到,竟然这样成功了!

      该方法纯属个人想出来的,还有诸多不完善之处,遇到同样问题的同学可以试试这个思路~

  • 相关阅读:
    Java 老兵不死,Kotlin 蓄势待发
    程序员写代码时戴着耳机,在听什么?
    推荐 7 个提升前端编程效率的 VSCode 插件
    去掉烦人的 !=null
    透析!软件开发未来 10 年的 8 个趋势
    10月01日总结
    09月29日总结
    09月28日总结
    09月27日总结
    09月26日总结
  • 原文地址:https://www.cnblogs.com/lvdabao/p/3414038.html
Copyright © 2011-2022 走看看