视频开发者必看:NV12、I420、I444、P010格式转换实战指南(附代码)

张开发
2026/4/15 20:22:58 15 分钟阅读

分享文章

视频开发者必看:NV12、I420、I444、P010格式转换实战指南(附代码)
视频开发者必看NV12、I420、I444、P010格式转换实战指南附代码在视频处理领域不同格式之间的转换是开发者经常需要面对的技术挑战。无论是直播推流、视频编辑还是图像处理理解并掌握主流视频格式的转换方法都至关重要。本文将深入探讨NV12、I420、I444和P010这四种常见格式的特点及相互转换的实现细节并提供可直接集成到项目中的代码示例。1. 视频格式基础解析视频格式的选择直接影响着处理效率、存储空间和图像质量。我们先来了解这四种格式的核心差异1.1 色彩采样与存储结构I420/YUV420P最通用的YUV格式4:2:0色度抽样平面存储Y、U、V三个独立平面每像素平均占用12bitY:8bitUV各2bitNV124:2:0色度抽样半平面存储Y平面独立UV交错存储硬件加速友好广泛用于移动设备和视频编码I444/YUV4444:4:4全色度抽样平面存储Y、U、V三个独立平面色彩信息最完整每像素占用24bitP0104:2:0色度抽样10bit精度存储为16bit半平面存储类似NV12但高位深HDR视频常用格式1.2 格式对比表特性I420NV12I444P010色度抽样4:2:04:2:04:4:44:2:0存储布局平面半平面平面半平面位深8bit8bit8bit10bit每像素大小12bit12bit24bit24bit硬件支持一般优秀较差优秀2. NV12与I420互转实现2.1 NV12转I420NV12和I420的主要区别在于UV分量的存储方式。转换时需要将交错的UV分量分离为独立的U和V平面。void NV12_to_I420(uint8_t* nv12, uint8_t* i420, int width, int height) { uint8_t* y_plane nv12; uint8_t* uv_plane nv12 width * height; uint8_t* i420_y i420; uint8_t* i420_u i420 width * height; uint8_t* i420_v i420_u (width * height) / 4; // 复制Y分量 memcpy(i420_y, y_plane, width * height); // 分离UV分量 for (int i 0; i width * height / 2; i) { if (i % 2 0) { // 偶数位是U分量 *i420_u uv_plane[i]; } else { // 奇数位是V分量 *i420_v uv_plane[i]; } } }注意NV12的UV平面大小为width×height/2而I420的U和V平面各为width/2×height/22.2 I420转NV12逆向转换需要将独立的U、V平面合并成交错的UV平面void I420_to_NV12(uint8_t* i420, uint8_t* nv12, int width, int height) { uint8_t* y_plane i420; uint8_t* u_plane i420 width * height; uint8_t* v_plane u_plane (width * height) / 4; uint8_t* nv12_y nv12; uint8_t* nv12_uv nv12 width * height; // 复制Y分量 memcpy(nv12_y, y_plane, width * height); // 合并UV分量 for (int i 0; i width * height / 4; i) { nv12_uv[2*i] u_plane[i]; // U分量 nv12_uv[2*i1] v_plane[i]; // V分量 } }3. 高精度格式P010的处理3.1 P010格式特点P010采用10bit精度存储但实际占用16bit空间。根据字节序不同有两种常见变体P010LE低10位有效高6位补0P010BE高10位有效低6位补03.2 P010转I420实现void P010LE_to_I420(uint8_t* p010, uint8_t* i420, int width, int height) { uint16_t* y_plane (uint16_t*)p010; uint16_t* uv_plane y_plane width * height; uint8_t* i420_y i420; uint8_t* i420_u i420 width * height; uint8_t* i420_v i420_u (width * height) / 4; // 转换Y分量取高10位 for (int i 0; i width * height; i) { uint16_t y y_plane[i] 6; // 取10bit有效值 y CLAMP(y, 64, 940); // 限制在合法范围 i420_y[i] (uint8_t)(y 2); // 10bit转8bit } // 分离并转换UV分量 for (int i 0; i width * height / 2; i) { uint16_t uv uv_plane[i]; if (i % 2 0) { // U分量 uint16_t u uv 0x03FF; // 取低10位 *i420_u (uint8_t)(u 2); } else { // V分量 uint16_t v uv 6; // 取高10位 *i420_v (uint8_t)(v 2); } } } #define CLAMP(x, min, max) ((x) (min) ? (min) : ((x) (max) ? (max) : (x)))4. 高级转换NV12与I444互转4.1 NV12转I444将4:2:0采样提升到4:4:4需要色度上采样void NV12_to_I444(uint8_t* nv12, uint8_t* i444, int width, int height) { uint8_t* y_plane nv12; uint8_t* uv_plane nv12 width * height; uint8_t* i444_y i444; uint8_t* i444_u i444 width * height; uint8_t* i444_v i444_u width * height; // 复制Y分量 memcpy(i444_y, y_plane, width * height); // 上采样UV分量 for (int y 0; y height; y) { for (int x 0; x width; x) { int uv_x x / 2; int uv_y y / 2; int uv_index uv_y * (width / 2) uv_x; // 双线性插值 uint8_t u uv_plane[2 * uv_index]; uint8_t v uv_plane[2 * uv_index 1]; i444_u[y * width x] u; i444_v[y * width x] v; } } }4.2 性能优化技巧SIMD指令加速使用SSE/AVX指令并行处理多个像素特别适合YUV分离/合并操作多线程处理将图像分块并行处理注意避免false sharing内存预分配复用内存缓冲区减少分配开销使用内存池管理临时缓冲区// 使用SSE加速的NV12转I420示例 void NV12_to_I420_SSE(uint8_t* nv12, uint8_t* i420, int width, int height) { // ...省略Y平面复制... // UV分离使用SSE const __m128i mask _mm_set1_epi16(0x00FF); for (int i 0; i width * height / 2; i 16) { __m128i uv _mm_loadu_si128((__m128i*)(nv12 width * height i)); __m128i u _mm_and_si128(uv, mask); __m128i v _mm_srli_epi16(uv, 8); _mm_storeu_si128((__m128i*)(i420 width * height i/2), u); _mm_storeu_si128((__m128i*)(i420 width * height * 5/4 i/2), v); } }在实际项目中我们发现对1080p视频进行格式转换时使用SIMD优化可以将转换速度提升3-5倍。特别是在实时视频处理场景中这种优化能显著降低CPU占用率。

更多文章