欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

最全面详细:JavaScript中YUV420P转换为RGB的完整解析

最编程 2024-08-15 15:43:21
...

刚加入一个音视频公司才一个月,因为之前从未接触过音视频领域,所以进来后几乎是两眼一抹黑,最近有个需求需要将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的详细讲解可以查看我在文末给出的参考文章。

image.png

如图,其中每一格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数据格式