Android Camera yuv數(shù)據(jù)直接渲染

前言

項(xiàng)目需要渲染相機(jī)預(yù)覽數(shù)據(jù),相機(jī)返回結(jié)果是yuv420sp數(shù)據(jù),也就是NV21數(shù)據(jù),排布格式是這樣的:


如果需要渲染到 surfaceview,則需要轉(zhuǎn) rgb數(shù)據(jù),再渲染,非常耗時(shí),如果直接使用yuv 數(shù)據(jù)當(dāng)做貼圖,使用自定義 shader來(lái)渲染,效率會(huì)非常高,在驍龍 870 上(紅米 K40)降了 50%(共 800%)的cpu 占用
目前demo 采用非常冷門的 irrlicht 渲染引擎,直接使用 opengl 也是一樣的邏輯
【特別注意】貼圖數(shù)據(jù)填充需要放到渲染線程內(nèi)

Android Image 接口

        yuvReader = ImageReader.newInstance(1280, 720, ImageFormat.YUV_420_888, 10);
        yuvReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = reader.acquireLatestImage();
                if (image == null) {
                    return;
                }
                ByteBuffer[] imageBuffers = new ByteBuffer[image.getPlanes().length];
                for (int i = 0; i < image.getPlanes().length; i++) {
                    imageBuffers[i] = image.getPlanes()[i].getBuffer();
                }
                Irrlicht.nativeUpdateImage(imageBuffers, 1280, 720, 1);
                image.close();
            }
        }, imageReadHandler);

如上接口可以直接獲取ByteBuffer[],包含了 yuv 數(shù)據(jù),分為了三個(gè)Plane,第一個(gè) Plane 是 y,第二個(gè) Plane 是 uv,特別注意【第三個(gè) Plane 是 vu,是第二個(gè) Plane 起始地址+1】,其實(shí)只需要用到前兩個(gè) Plane 即可

native 層獲取 yuv 數(shù)據(jù)如下:

        jobject bufferObj = env->GetObjectArrayElement(image_buffer, 0);
        if (yData == nullptr) {
            yData = static_cast<uint8_t *>(malloc(h * w));
        }
        memcpy(yData, env->GetDirectBufferAddress(bufferObj), h * w);
        env->DeleteLocalRef(bufferObj);

        bufferObj = env->GetObjectArrayElement(image_buffer, 1);
        if (uvData == nullptr) {
            uvData = static_cast<uint8_t *>(malloc(h * w / 2));
        }
        memcpy(uvData, env->GetDirectBufferAddress(bufferObj), h * w / 2);
        env->DeleteLocalRef(bufferObj);

如果采用 opencv 轉(zhuǎn)為 rgb 的話

jobject bufferObj = env->GetObjectArrayElement(image_buffer, 0);
cv::Mat y_plane(h, w, CV_8UC1, env->GetDirectBufferAddress(bufferObj));
env->DeleteLocalRef(bufferObj);
bufferObj = env->GetObjectArrayElement(image_buffer, 1);
cv::Mat uv_plane(h / 2, w / 2, CV_8UC2, env->GetDirectBufferAddress(bufferObj));
env->DeleteLocalRef(bufferObj);
cv::cvtColorTwoPlane(y_plane, uv_plane, cameraRGBMat, cv::COLOR_YUV2BGR_NV21);

可以直接將 rgb 數(shù)據(jù)渲染到 2dImage

imageTexture = driver->addTexture(core::dimension2d<u32>(cameraWidth, cameraHeight), "$cameraImage");
auto *frame_buf = cameraRGBMat.data;
auto *tex_buf = (uint8_t *) imageTexture->lock();
for (int j = 0; j < cameraRGBMat.rows * cameraRGBMat.cols; j++) {
    *(tex_buf) = *(frame_buf + 2);
    *(tex_buf + 1) = *(frame_buf + 1);
    *(tex_buf + 2) = *(frame_buf);
    *(tex_buf + 3) = 255;
    frame_buf += 3;
    tex_buf += 4;
}
imageTexture->unlock();

device->getVideoDriver()->enableMaterial2D(true);
auto sd = device->getVideoDriver()->getCurrentRenderTargetSize();
device->getVideoDriver()->draw2DImage(imageTexture,
                                      core::rect<s32>(0, 0, sd.Width,
                                                      sd.Height),
                                      core::rect<s32>(0, 0,
                                                      imageTexture->getSize().Width,
                                                      imageTexture->getSize().Height));
device->getVideoDriver()->enableMaterial2D(false);

創(chuàng)建YUV貼圖,并將yuv數(shù)據(jù)復(fù)制過(guò)去

        yuvTexture = driver->addTexture(core::dimension2d<u32>(cameraWidth, cameraHeight),
                                        "yuvTexture", ECF_A8R8G8B8);

這里為了簡(jiǎn)化操作,先將 yuv420 數(shù)據(jù)擴(kuò)充為 yuv444 數(shù)據(jù),并直接復(fù)制到texture的buffer 里,要注意和 shader 里的 rgba 對(duì)應(yīng)關(guān)系,我這里是ECF_A8R8G8B8, buf對(duì)應(yīng)b,buf+3對(duì)應(yīng) a,在 shader 里取值的時(shí)候要對(duì)應(yīng)好

        auto buf = static_cast<uint8_t *>(yuvTexture->lock());
        auto *y_buf = yData;
        int width = yuvTexture->getSize().Width;
        int height = yuvTexture->getSize().Height;

        for (int h = 0; h < height; ++h) {
            auto *uv_buf_row = uvData + int(h / 2) * width; // 每?jī)尚泄蚕硪唤MUV
            for (int w = 0; w < width; ++w) {
                // Y分量
                *buf = *y_buf;
                y_buf += 1;

                // UV分量
                auto *uv_buf_pixel = uv_buf_row + int(w / 2) * 2; // 每?jī)蓚€(gè)像素共享一組UV
                *(buf+1) = *uv_buf_pixel; // U分量
                *(buf+2) = *(uv_buf_pixel + 1); // V分量

                buf += 4;
            }
        }

        yuvTexture->unlock();

頂點(diǎn)和三角形設(shè)計(jì)

定義圖像的四個(gè)頂點(diǎn)

        Vertices[0] = S3DVertex(-1.0f, -1.0f, 0.0f, // 位置,對(duì)應(yīng) shader 的inVertexPosition
                                1.0f, 1.0f, 1.0f, // 法向量,可以忽略
                                video::SColor(255, 255, 255, 255), // 頂點(diǎn)顏色,可以忽略
                                0.0f, 1.0f); // 貼圖 uv 坐標(biāo),對(duì)應(yīng) shader 的inTexCoord0
        Vertices[1] = S3DVertex(1.0f, -1.0f, 0.0f,
                                1.0f, 1.0f, 1.0f,
                                video::SColor(255, 255, 255, 255),
                                1.0f, 1.0f);
        Vertices[2] = S3DVertex(1.0f, 1.0f, 0.0f,
                                1.0f, 1.0f, 1.0f,
                                video::SColor(255, 255, 255, 255),
                                1.0f, 0.0f);
        Vertices[3] = S3DVertex(-1.0f, 1.0f, 0.0f,
                                1.0f, 1.0f, 1.0f,
                                video::SColor(255, 255, 255, 255),
                                0.0f, 0.0f);

定義三角形頂點(diǎn)的順序
只需要定義兩個(gè)三角形即可表達(dá)一個(gè)矩形畫(huà)面

u16 indices[] = {2, 1, 3, 1, 0, 3};

driver->drawIndexedTriangleList(&Vertices[0], 4, &indices[0], 2);

頂點(diǎn)著色器 YUVVertexShader.glsl

由于直接鋪滿整個(gè)屏幕,所以頂點(diǎn)坐標(biāo)可以直接對(duì)應(yīng)到屏幕坐標(biāo),圖像坐標(biāo)和貼圖坐標(biāo)透?jìng)骷纯?br> 注意,irrlicht 默認(rèn)glBindAttribLocation了頂點(diǎn)坐標(biāo)和貼圖坐標(biāo),名字寫死了是inVertexPosition和inTexCoord0,如果是 opengl 的話要注意對(duì)齊

// 指定精度
precision mediump float;

attribute vec3 inVertexPosition; // 頂點(diǎn)坐標(biāo),名字和glBindAttribLocation對(duì)齊
attribute vec2 inTexCoord0; // 貼圖 uv 坐標(biāo),名字和glBindAttribLocation對(duì)齊

// 傳遞給片段著色器的紋理坐標(biāo)
varying vec2 tc; // 名字和片元著色器對(duì)齊

void main()
{
    // 設(shè)置頂點(diǎn)位置
    gl_Position = vec4(inVertexPosition, 1.0);
    // 傳遞紋理坐標(biāo)
    tc = inTexCoord0;
}

片元著色器 YUVFragmentShader.glsl

yuv數(shù)據(jù)是放在 argb 數(shù)據(jù)里傳遞的,所以需要對(duì)應(yīng)取出,然后進(jìn)行轉(zhuǎn)換即可

precision mediump float;
varying mediump vec2 tc;
uniform sampler2D yuvTexture;

mat3 yuvToRgb = mat3(
    1,       1,       1,
    0,      -0.39465,   2.03211,
    1.13983,        -0.58060,   0
);

void main()
{
    mediump float y = texture2D(yuvTexture, tc).b; // Y 分量
    mediump vec2 uv = texture2D(yuvTexture, tc).gr - 0.5;  // UV 分量

//    float r = y + 1.13983 * uv.y;
//    float g = y - 0.39465 * uv.x - 0.58060 * uv.y;
//    float b = y + 2.03211 * uv.x;
//    gl_FragColor = vec4(r,g,b, 1.0);

    vec3 rgb = yuvToRgb * vec3(y, uv);
    gl_FragColor = vec4(rgb, 1.0);
}

加載 shader,進(jìn)行渲染

IVideoDriver *driver = SceneManager->getVideoDriver();
video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
s32 materialType = gpu->addHighLevelShaderMaterialFromFiles(
                        mediaPath + "Shaders/YUVVertexShader.glsl",
                        mediaPath + "Shaders/YUVFragmentShader.glsl", 0);
SMaterial Material;
Material.Wireframe = false;
Material.Lighting = false;
Material.ZBuffer = video::ECFN_DISABLED; // 不做深度測(cè)量,也就是和其他元素沒(méi)有前后遮蓋
Material.MaterialType = (video::E_MATERIAL_TYPE) materialType;
Material.setTexture(0, yuvTexture);
driver->setMaterial(Material);
driver->setTransform(ETS_WORLD, AbsoluteTransformation);
// 繪制三角形
driver->drawIndexedTriangleList(&Vertices[0], 4, &indices[0], 2);

后語(yǔ)

uv 通道數(shù)據(jù)還可以獨(dú)立出來(lái)一個(gè) texture傳入 shader,這里由于 irrlicht 只支持 32 位的 texture,不支持直接創(chuàng)建 8 位和 16 位的 texture, y 和 uv 分開(kāi)的話有點(diǎn)浪費(fèi)空間,干脆擴(kuò)充成 yuv444 再傳入了,也可以把 y 壓縮到和 uv 同寬高再輸入

y 壓縮到和 uv 同寬高

其他邏輯都不用變,只需要改下 Texture 的寬高,以及數(shù)據(jù)填充邏輯

yuvTexture = driver->addTexture(core::dimension2d<u32>(cameraWidth/2, cameraHeight/2),
                                        "yuvTexture", ECF_A8R8G8B8);

auto buf = static_cast<uint8_t *>(yuvTexture->lock());
int width = yuvTexture->getSize().Width;
int height = yuvTexture->getSize().Height;
auto *uv_buf = uvData;
for (int h = 0; h < height; ++h) {
    auto *y_buf_row = yData + h * 2 * width * 2; // 壓縮了 y
    for (int w = 0; w < width; ++w) {
        // Y分量
        *buf = *(y_buf_row + w * 2);

        // UV分量
        *(buf + 1) = *uv_buf; // U分量
        *(buf + 2) = *(uv_buf + 1); // V分量
        uv_buf += 2;
        buf += 4;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容