一、获取摄像头数据并渲染
通过navigator.mediaDevices.getUserMedia获取到摄像头的数据,失败返回错误信息。代码如下:
let constraints = {
video : {
width: 640, // 宽带
height: 480, // 高度
frameRate:15, // 帧率
},
audio : false
}
// 获取到video元素,用于播放视频轨道
const video = document.getElementById('video');
let track;
navigator.mediaDevices.getUserMedia(constraints)
.then((stream) => {
track = stream.getVideoTracks()[0];
video.srcObject = stream;
video.play();
})
.catch((error) => {
console.error(error);
});
通过stream.getVideoTracks()[0];从媒体流中获取到视频轨道数据。
这里还要注意一些浏览器不能兼容navigator.mediaDevices.getUserMedia方法。
二、将视频轨道数据转换为可操作的Uint8ClampedArray数据
// 创建一个canvas元素,用于绘制视频轨道
const canvas = document.getElementById('canvas');
canvas.width = 640;
canvas.height = 480;
const canvasContext = canvas.getContext('2d');
// 在canvas上绘制视频帧
canvasContext.drawImage(video, 0, 0, canvas.width, canvas.height);
// 获取像素数据
const imageData = canvasContext.getImageData(0, 0, canvas.width, canvas.height);
const pixelData = imageData.data;
其中pixeData为RGBA数据,Uint8ClampedArray类型数据。
三、将RGBA数据转换为YUV420数据
YUV420介绍
YUV为三个分量,Y表示明亮度;而U和V表示的则是色度,作用是描述影像色彩及饱和度,用于指定像素的颜色。其主要的采用方式为,YUV4:4:4,YUV4:2:2,YUV4:2:0。其中YUV4:2:0为每四个Y公用一组UV,该方式最为常用。
YUV420又分为YUV420p和YUV420sp,而YUV420p又分为YV12和I420;
内存存储
I420:Y1,Y2,Y3,Y4,U1,V1 存储四个像素点。
即就可以计算出一个YUV420在内存中存放的大小:Y = width * hight,U = Y / 4,V = Y / 4,即一张YUV图像的存储空间就是 width * height *3 /2。
转换公式
// Convert RGB to YUV
let y = 0.299 * r + 0.587 * g + 0.114 * b;
let u = -0.14713 * r - 0.28886 * g + 0.436 * b;
let v = 0.615 * r - 0.51498 * g - 0.10001 * b;
综上就可得出转换方法:
function toYUV(rgba, width, height) {
let yuv = new Uint8ClampedArray(width * height * 3 / 2);
let yIndex = 0;
let uIndex = width * height;
let vIndex = uIndex + (uIndex / 4);
for (let j = 0; j < height; j++) {
for (let i = 0; i < width; i++) {
let r = rgba[(j * width + i) * 4];
let g = rgba[(j * width + i) * 4 + 1];
let b = rgba[(j * width + i) * 4 + 2];
// Convert RGB to YUV
let y = 0.299 * r + 0.587 * g + 0.114 * b;
let u = -0.14713 * r - 0.28886 * g + 0.436 * b;
let v = 0.615 * r - 0.51498 * g - 0.10001 * b;
// Write the YUV values to the output array
yuv[yIndex++] = Math.max(0, Math.min(255, y));;
// Write the U and V values every other pixel
if (i % 2 == 0 && j % 2 == 0) {
yuv[uIndex++] = Math.max(0, Math.min(255, u + 128));
yuv[vIndex++] = Math.max(0, Math.min(255, v + 128));
}
}
}
return yuv;
}
该方法遍历输入的RGBA数组中的每个像素,对每个像素进行RGB到YUV的转换,并将结果写入一个新的Uint8ClampedArray中。然后,它按照YUV420格式重新组织数据,将Y值存储在输出数组的前半部分,将U和V值每隔一个像素存储在输出数组的后半部分。
在转换RGB到YUV时,该函数使用的是ITU-R BT.601的标准系数。在将U和V值存储回输出数组之前,它将它们平移了128个单位,以将它们转换为无符号8位值。最后,函数将整个图像的宽度和高度作为参数,并使用它们来计算输出数组中每个分量的索引。
请注意,此函数假定输入RGBA数据是一个按行存储的一维数组,并且每个像素使用4个字节(即RGBA格式)。如果输入数据格式不同,需要根据情况修改该函数。
四、调用转换方法并保存到文件播放
function render() {
// 在canvas上绘制视频帧
canvasContext.drawImage(video, 0, 0, canvas.width, canvas.height);
// 获取像素数据
const imageData = canvasContext.getImageData(0, 0, canvas.width, canvas.height);
const pixelData = imageData.data;
let yuv = toYUV(pixelData, canvas.width, canvas.height);
fs.appendFile('./video.yuv', yuv, (err) => {
if (err) {
console.error(err);
return;
}
});
requestAnimationFrame(render);
}
// render();
setTimeout(render, 500);
该方法将每一帧的RGBA数据转换为YUV数据并保存到video.yuv文件中。使用FFmpeg、YUVplayer等播放器可以播放yuv文件。
请注意,应设置播放器对应的格式和宽高。