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();
关键优化建议:
- 优先选择OpenCV方案:native级转换速度远快于Java层操作,适合实时视频编码
- 确保宽高为偶数:NV21的UV分量采用2x2采样,奇数宽高可能导致编码异常
- 复用对象:避免频繁创建Mat、Allocation等对象,减少内存开销和GC次数
内容的提问来源于stack exchange,提问作者kfir
相关产品推荐
相关产品推荐

