You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中如何将OpenCV Mat转换为NV21字节数组适配MediaCodec

解决方案:将OpenCV Mat或Bitmap转换为NV21字节数组

针对Android平台下MediaCodec编码MP4的需求,以下是几种高效的转换方案,优先推荐OpenCV原生转换(速度最快),其次是优化的Bitmap转NV21方法,最后提供兼容AndroidX的RenderScript备选方案。


方案1:直接从OpenCV Mat转换为NV21(最优选择)

利用OpenCV的native级颜色转换,避免Java层的低效操作,适合实时视频处理场景。

注意事项:

  • 确保输入的Mat是RGB格式(如CV_8UC3)
  • OpenCV 4.x及以上版本支持直接转换为NV21;若版本较低,需手动处理I420到NV21的转换

代码示例(OpenCV 4.x+):

// 假设mImageGrab是RGB格式的Mat(CV_8UC3)
int width = mImageGrab.cols();
int height = mImageGrab.rows();
// 创建NV21格式的Mat,尺寸为 width × (height + height/2)
Mat nv21Mat = new Mat(height + height/2, width, CvType.CV_8UC1);
// 直接将RGB转换为NV21
Imgproc.cvtColor(mImageGrab, nv21Mat, Imgproc.COLOR_RGB2YUV_NV21);
// 将Mat数据提取为字节数组
byte[] nv21Bytes = new byte[width * height * 3 / 2];
nv21Mat.get(0, 0, nv21Bytes);

兼容低版本OpenCV(手动转NV21):

如果你的OpenCV版本没有COLOR_RGB2YUV_NV21,可以先转成I420,再将U/V平面交换并交织:

// 先转成I420格式
Mat i420Mat = new Mat(height + height/2, width, CvType.CV_8UC1);
Imgproc.cvtColor(mImageGrab, i420Mat, Imgproc.COLOR_RGB2YUV_I420);

// 分离Y、U、V平面
Mat yPlane = i420Mat.rowRange(0, height);
Mat uPlane = i420Mat.rowRange(height, height + height/4);
Mat vPlane = i420Mat.rowRange(height + height/4, i420Mat.rows());

// 生成NV21字节数组
byte[] nv21Bytes = new byte[width * height * 3 / 2];
// 复制Y平面数据
yPlane.get(0, 0, nv21Bytes);

// 交织V和U数据(NV21是Y后接VU交织)
int uvIndex = width * height;
int uvSize = width * height / 4;
for (int i = 0; i < uvSize; i++) {
    nv21Bytes[uvIndex++] = vPlane.get(i / width, i % width)[0];
    nv21Bytes[uvIndex++] = uPlane.get(i / width, i % width)[0];
}

方案2:优化的Bitmap转NV21方法

若必须从Bitmap转换,可使用优化的YUV计算逻辑,比encodeYUV420SP更高效(减少冗余计算):

public static byte[] bitmapToNV21(Bitmap bitmap) {
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    int[] argbPixels = new int[width * height];
    bitmap.getPixels(argbPixels, 0, width, 0, 0, width, height);

    byte[] nv21 = new byte[width * height * 3 / 2];
    int yIdx = 0;
    int uvIdx = width * height;

    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            int pixel = argbPixels[i * width + j];
            int r = (pixel >> 16) & 0xFF;
            int g = (pixel >> 8) & 0xFF;
            int b = pixel & 0xFF;

            // 计算Y分量
            int y = (int) (0.299 * r + 0.587 * g + 0.114 * b);
            nv21[yIdx++] = (byte) Math.max(0, Math.min(255, y));

            // 每2x2像素计算一次UV分量(NV21采样规则)
            if (i % 2 == 0 && j % 2 == 0) {
                int u = (int) (-0.14713 * r - 0.28886 * g + 0.436 * b);
                int v = (int) (0.615 * r - 0.51499 * g - 0.10001 * b);
                u += 128;
                v += 128;
                nv21[uvIdx++] = (byte) Math.max(0, Math.min(255, v));
                nv21[uvIdx++] = (byte) Math.max(0, Math.min(255, u));
            }
        }
    }
    return nv21;
}

方案3:兼容AndroidX的RenderScript备选方案

尽管RenderScript已被标记为弃用,但AndroidX提供了兼容库,适合需要快速实现Bitmap转NV21的场景:

步骤1:添加依赖

在build.gradle(Module级别)中添加:

implementation 'androidx.renderscript:renderscript:1.0.0'

步骤2:转换工具类

import android.content.Context;
import android.graphics.Bitmap;
import androidx.renderscript.Allocation;
import androidx.renderscript.Element;
import androidx.renderscript.RenderScript;
import androidx.renderscript.ScriptIntrinsicRGBToYuv;
import androidx.renderscript.Type;

public class RSNV21Converter {
    private final RenderScript rs;

    public RSNV21Converter(Context context) {
        rs = RenderScript.create(context);
    }

    public byte[] convertBitmapToNV21(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();

        // 创建RGB输入Allocation
        Allocation rgbAlloc = Allocation.createFromBitmap(rs, bitmap);
        // 创建NV21输出Allocation
        Type yuvType = new Type.Builder(rs, Element.U8(rs))
                .setX(width)
                .setY(height)
                .setYuvFormat(android.graphics.ImageFormat.NV21)
                .create();
        Allocation yuvAlloc = Allocation.createTyped(rs, yuvType);

        // 执行RGB到NV21的转换
        ScriptIntrinsicRGBToYuv rgbToYuv = ScriptIntrinsicRGBToYuv.create(rs, Element.U8(rs));
        rgbToYuv.setInput(rgbAlloc);
        rgbToYuv.forEach(yuvAlloc);

        // 提取字节数组
        byte[] nv21Bytes = new byte[width * height * 3 / 2];
        yuvAlloc.copyTo(nv21Bytes);

        // 释放资源
        rgbAlloc.destroy();
        yuvAlloc.destroy();
        rgbToYuv.destroy();
        return nv21Bytes;
    }

    public void release() {
        rs.destroy();
    }
}

使用示例:

RSNV21Converter converter = new RSNV21Converter(getApplicationContext());
byte[] nv21Data = converter.convertBitmapToNV21(yourBitmap);
// 使用完记得释放资源
converter.release();

关键优化建议:

  1. 优先选择OpenCV方案:native级转换速度远快于Java层操作,适合实时视频编码
  2. 确保宽高为偶数:NV21的UV分量采用2x2采样,奇数宽高可能导致编码异常
  3. 复用对象:避免频繁创建Mat、Allocation等对象,减少内存开销和GC次数

内容的提问来源于stack exchange,提问作者kfir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 23:48:22