使用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秒后的绘制图片动作会捕捉到异常。没想到,竟然这样成功了!

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

posted @ 2013-11-08 12:30  吕大豹  阅读(3461)  评论(1编辑  收藏  举报