最全面详细:JavaScript中YUV420P转换为RGB的完整解析
刚加入一个音视频公司才一个月,因为之前从未接触过音视频领域,所以进来后几乎是两眼一抹黑,最近有个需求需要将YUV420P格式的视频数据转换为RGB数据,于是我开启全网搜索模式,连chatGPT都用上了,结果还是无法写出完美转换的代码,就在我为此掉了大把头发的时候,我狠下心在全网查看YUV420P的原理,经过我的潜心研究终于将YUV420P搞清楚了,现将我的学习成果与大家分享。
YUV模型是根据一个亮度(Y分量)和两个色度(UV分量)来定义颜色空间,常见的YUV格式有YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411、YUV420等,其中比较常见的YUV420分为两种:YUV420P和YUV420SP。关于YUV420P的详细讲解可以查看我在文末给出的参考文章。
如图,其中每一格Y是一个像素,其中从第一个开始,每行的长度应为画面的宽度,图中表示的是一幅宽度为6,高度为4的画面,从物理空间的排布来看,这里像素的排布顺序应该是Y1 Y2 Y7 Y8 Y3 Y4 Y9 Y10 Y5 Y6 Y11 Y12 Y13 Y14 Y19 Y20 Y15 Y16 Y21 Y22 Y17 Y18 Y23 Y24;图片中相邻的四个像素共用一个UV值,所以Y1 Y2 Y7 Y8 是共用U1和V1的,于是我们只需要用js写出能将这些关系对应上的代码就能将数据转换成RGB数据了。
下面是我的代码
这是YUV转RGB的方法
yuvToRgb: function (y, u, v) {
// YUV到RGB的转换公式
var r = y - 16 + 1.372 * (v - 128);
var g = y - 16 - 0.337 * (u - 128) - 0.699 * (v - 128);
var b = y - 16 + 1.734 * (u - 128);
// var r = y + 1.13983 * (v - 128);
// var g = y - 0.39465 * (u - 128) - 0.58060 * (v - 128);
// var b = y + 2.03211 * (u - 128);
// 确保RGB值在0~255之间
r = Math.min(255, Math.max(0, r));
g = Math.min(255, Math.max(0, g));
b = Math.min(255, Math.max(0, b));
// 返回RGB值
return [r, g, b];
}
这是YUV420P转RGB的方法
/**
* @param {Uint8Array} videoFrame 原始YUV420P数据
* @param {number} width 原始数据的宽度
* @param {number} height 原始数据的高度
*/
function YUV420PToRGBA(videoFrame) {
const pixels = new Uint8Array(width * height * 4); // 用来存储rgba数据,长度是宽乘以高的四倍
const uOffset = width * height;
const vOffset = (width >> 1) * (height >> 1);
const ys = videoFrame.subarray(0, uOffset); // 将原始数据中的Y数据抽离出来
const us = videoFrame.subarray(uOffset, uOffset + vOffset); // 将原始数据中的U数据抽离出来
const vs = videoFrame.subarray(uOffset + vOffset, videoFrame.length); // 将原始数据中的V数据抽离出来
let count = 0; // 计算U数据或V数据的下标
for (let h = 0; h < height; h+=2) {
for (let w = 0; w < width; w+=2) {
const i1 = w + (h * width) // 计算第一个像素(如Y1)的下标
const rgb1 = Utils.yuvToRgb(ys[i1], us[count], vs[count])
pixels[i1 * 4] = rgb1[0]
pixels[i1 * 4 + 1] = rgb1[1]
pixels[i1 * 4 + 2] = rgb1[2]
pixels[i1 * 4 + 3] = 1
const i2 = (w + 1) + (h * width) // 计算第二个像素(如Y2)的下标
const rgb2 = Utils.yuvToRgb(ys[i2], us[count], vs[count])
pixels[i2 * 4] = rgb2[0]
pixels[i2 * 4 + 1] = rgb2[1]
pixels[i2 * 4 + 2] = rgb2[2]
pixels[i2 * 4 + 3] = 1
const i3 = w + ((h + 1) * width) // 计算第三个像素(如Y7)的下标
const rgb3 = Utils.yuvToRgb(ys[i3], us[count], vs[count])
pixels[i3 * 4] = rgb3[0]
pixels[i3 * 4 + 1] = rgb3[1]
pixels[i3 * 4 + 2] = rgb3[2]
pixels[i3 * 4 + 3] = 1
const i4 = (w + 1) + ((h + 1 )* width) // 计算第四个像素(如Y8)的下标
const rgb4 = Utils.yuvToRgb(ys[i4], us[count], vs[count])
pixels[i4 * 4] = rgb4[0]
pixels[i4 * 4 + 1] = rgb4[1]
pixels[i4 * 4 + 2] = rgb4[2]
pixels[i4 * 4 + 3] = 1
count += 1
}
}
}
用上面的代码是可以将YUV420P转为RGB的,若有问题欢迎在评论区讨论。
参考文章1:YUV模型:YUV420P和YUV420SP
参考文章2:图文详解YUV420数据格式