Javascript Canvas getImageData在某些移动设备上返回不正确的数据

Javascript Canvas getImageData在某些移动设备上返回不正确的数据,javascript,html,canvas,Javascript,Html,Canvas,我正在开发一款基于视频帧的画布视频播放器,该播放器具有一些特殊功能。为了克服视频HTML5标签中不可靠的计时问题,我们正在使用的视频在每个帧中嵌入一个条形码,指示当前帧号。使用canvas getImageData方法,我可以抓取像素并读取条形码以获得帧号。这很好,我有一个演示,证明了它的工作原理(我无法在这个小提琴中绕过CORS,将视频提供给画布,因此要看到它工作,您必须在本地下载示例视频,然后通过按钮上传。虽然不理想,但它可以工作) 在某些移动设备上(到目前为止只有安卓),这种逻辑被打破了。

我正在开发一款基于视频帧的画布视频播放器,该播放器具有一些特殊功能。为了克服视频HTML5标签中不可靠的计时问题,我们正在使用的视频在每个帧中嵌入一个条形码,指示当前帧号。使用canvas getImageData方法,我可以抓取像素并读取条形码以获得帧号。这很好,我有一个演示,证明了它的工作原理(我无法在这个小提琴中绕过CORS,将视频提供给画布,因此要看到它工作,您必须在本地下载示例视频,然后通过按钮上传。虽然不理想,但它可以工作)

在某些移动设备上(到目前为止只有安卓),这种逻辑被打破了。getImageData返回不正确的值

它在我的三星Galaxy S5 v6.0.1上正常工作,但在运行android v7.1.2的谷歌像素上失败。我将尝试收集它在哪些设备/操作系统版本上发生故障的更多数据

例如,在桌面上播放时,getImageData的第一次迭代返回:

Uint8ClampedArray(64) [3, 2, 3, 255, 255, 255, 255, 255, 246, 245, 247, 255, 243, 242, 243, 255, 241, 239, 241, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255]
正确地计算为帧号1

然而,在银河系上,第一次迭代返回:

Uint8ClampedArray(64) [255, 242, 217, 255, 255, 234, 209, 255, 41, 1, 1, 255, 254, 235, 210, 255, 255, 234, 209, 255, 50, 4, 0, 255, 254, 240, 215, 255, 255, 248, 224, 255, 255, 249, 225, 255, 255, 251, 232, 255, 255, 252, 233, 255, 255, 252, 233, 255, 255, 253, 234, 255, 255, 255, 237, 255, 255, 255, 237, 255, 28, 1, 1, 255] 
我了解到某些设备可能正在进行额外的平滑处理,因此我一直在尝试通过以下方式在上下文中禁用它:

this.ctx.mozImageSmoothingEnabled = false;
this.ctx.webkitImageSmoothingEnabled = false;
this.ctx.msImageSmoothingEnabled = false;
this.ctx.imageSmoothingEnabled = false; 
但这没用

下面是JSFIDLE中使用的代码

var frameNumberDiv = document.getElementById('frameNumber');
function load() {
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    var video = document.getElementById('video');
    canvas.width = 568;
    canvas.height = 640;
    video.addEventListener('play', function() {
        var that = this; //cache
        (function loop() {
            if (!that.paused && !that.ended) {
                ctx.drawImage(that, 0, 0);
                var pixels = ctx.getImageData(0, 320 - 1, 16, 1).data;
                getFrameNumber(pixels);
                setTimeout(loop, 1000 / 30); // drawing at 30fps
            }
        })();
    }, 0);
}

function getFrameNumber(pixels) {

    let j = 0;
    let thisFrameNumber = 0;
    let str = "Pixels: ";
    for (let i = 0; i < 16; i++) {
        str += pixels[j] + " ";
        thisFrameNumber += getBinary(pixels[j], i);
        j += 4;
    }
    document.getElementById('frameNumber').innerHTML = "FrameNumber: " + thisFrameNumber;
}

function getBinary(pixel, binaryPlace) {
    const binary = [1, 2, 4, 8, 16, 32, 64, 128, 256,
        512, 1024, 2048, 4096, 8192, 16384, 32768
    ];
    if (pixel > 128) return 0;
    if (pixel < 128 && binary[binaryPlace]) {
        return binary[binaryPlace]
    } else {
        return 0;
    }
}

(function localFileVideoPlayer() {
    'use strict';
    var URL = window.URL || window.webkitURL;
    var displayMessage = function(message, isError) {
        var element = document.querySelector('#message');
        element.innerHTML = message;
        element.className = isError ? 'error' : 'info';
    }
    var playSelectedFile = function(event) {
            console.log("Playing");
        var file = this.files[0];
        var type = file.type;
        var videoNode = document.querySelector('video');
        var canPlay = videoNode.canPlayType(type);
        if (canPlay === '') canPlay = 'no';
        var message = 'Can play type "' + type + '": ' + canPlay;
        var isError = canPlay === 'no';
        displayMessage(message, isError);

        if (isError) {
            return;
        }

        var fileURL = URL.createObjectURL(file);
        videoNode.src = fileURL;
        load();
    }
    var inputNode = document.querySelector('input')
    inputNode.addEventListener('change', playSelectedFile, false)
})();
正如您可能注意到的,结果似乎是正确的,但是它们向左移动了一个像素

我稍微修改了一点,将读取的getImageData偏移一个像素,它给出了与像素上完全相同的响应

var pixels = ctx.getImageData(1, 320 - 1, 16, 1).data;
做-1似乎没有效果

因此,出于某种原因,这些设备要么将整个纹理移动一个像素,要么getImageData方法有问题

编辑4

作为一个实验,我重新配置了我的代码以使用webGL纹理。桌面/移动设备上的相同行为。这允许我使用gl.readPixels将-1用作x目标。我希望通过跳过使用画布,整个图像将存储在内存中,我可以访问我需要的像素数据。。。。虽然不起作用,但这是它生成的数据,表明它也使用纯webGL进行了转换

Uint8Array(64) [0, 0, 0, 0, 255, 248, 248, 255, 25, 18, 18, 255, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]

Uint8Array(64) [0, 0, 0, 0, 255, 254, 247, 255, 255, 244, 236, 255, 48, 18, 10, 255, 254, 246, 238, 255, 255, 247, 239, 255, 255, 247, 239, 255, 255, 248, 241, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 250, 240, 255, 255, 250, 240, 255]

Uint8Array(64) [0, 0, 0, 0, 31, 0, 0, 255, 254, 243, 230, 255, 43, 6, 1, 255, 254, 243, 230, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 229, 255, 255, 244, 229, 255]
使用:

gl.readPixels(-1, height, 16, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
编辑5

好的,我保证我会得到更多关于哪些设备出现故障/未出现故障的详细信息。我用一个稍微修改过的软件做了一些在线QA测试。我对它做了一些修改,使它更适合大众使用

不幸的是,反应相当复杂。我希望它能独立于安卓7,但事实似乎并非如此


我在我的谷歌硬盘上有一个测试结果。并不是说这些测试是100%可靠的,但似乎只是一些随机设备……

您的代码很好,所以这里的问题在于一些机器人如何呈现您的条形码。条形码的实现太小(16x1像素),并且是左缩进的

如果将设备在外部像素上产生的任何反别名缩进,将弄乱条形码,并给出错误的结果,因此,在使用视频渲染时,您肯定不希望在单像素安全区域工作

我的建议是将条形码重做到一个更大的尺寸——比如说18像素的高度——只使用黑白(没有灰色),在视频上居中,线条的其余部分将其涂成绿色:(在本例中,它是“1”的条形码)

然后制作一个完整的320x16的GetImageData,去掉所有RGB为0x255x0(和近似值)的东西,您就有了可以用来获取帧号的条形码


我知道您可能希望得到一个答案,即不需要重做视频,但在本例中,问题出在源代码上。

我也遇到过同样的问题,但就是找不到答案。在我的案例中,我终于找到了答案,听起来99%像你的问题

这是像素密度

您提到的所有设备上的像素密度都不同,可能具有1 dpr(设备像素比率)的设备工作正常,而其他设备则不正常

因此,在我使用p5js的情况下,我将像素密度设置为1,它就像一个魅力

pixelDensity(1);
因此,将其设置为1 dpr,您很可能就可以开始了


我希望这能帮助一些人,因为我在这个问题上花了很长时间。

这个设备上的
videoNode.videoHeight
videoNode.videoWidth
的值是多少?如果不硬编码画布的宽度和高度,并且安装时使用这些值以及
drawImage(vid,0,0,vid.videoWidth,vid.videoHeight)
,会发生什么情况。所有的图像都画在画布的所有表面上了吗?它是否仅在播放视频时发生(即视频暂停时是否有效?)嘿@kaido我已进行了编辑以解决您的问题。谢谢对于videoHeight/videoWidth,您需要等待onloadedmetadata事件。不能是0。你是怎么写条形码的?@clabe45对不起,我不知道。它们是由另一个团队制作的。我猜应该是后效。你也可以使用:window.devicePixelRatio访问像素密度
pixelDensity(1);