iOS开发中,需要把jpg图片转换成i420,然后进行旋转,再转换为CVPixelBufferRef,最后转换为CMSampleBufferRef。特作此记录。以下代码 filePath 是图片存储路径。
需要注意的是,图片的宽高需要是偶数,奇数会转换错误。
代码里使用到了libyuv库,同时libyuv依赖libjpeg库,我已经编译好了libjpeg-8.0 和9.0的库,需要的可以自行下载:https://github.com/ayangcool/leo_libjpeg
+ (CVPixelBufferRef)jpgConvertToI420:(NSString *)filePath rotate:(BOOL)rotate {
if (!filePath || filePath.length <= 0) {
return nil;
}
NSData *imgData = [NSData dataWithContentsOfFile:filePath];
if (!imgData) {
return nil;
}
UIImage *image = [UIImage imageWithData:imgData];
int img_width = image.size.width;
int img_height = image.size.height;
if (img_width % 2 != 0) {
img_width++;
}
if (img_height % 2 != 0) {
img_height++;
}
// 1.转换为I420数据
int dst_plane1_stride = img_width;
// 步长必须是16的倍数,因为涉及到字节对齐,而且iOS13和之前的版本处理方式不一样,要注意
int stride_length = 16;
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
stride_length = 64;
} else {
stride_length = 16;
}
if ((img_width % stride_length) != 0) {
dst_plane1_stride = (img_width / stride_length + 1) * stride_length;
}
int dst_plane2_stride = dst_plane1_stride;
int dst_plane1_height = img_height;
int dst_plane2_height = img_height / 2;
int dst_plane1_size = dst_plane1_stride * dst_plane1_height;
int dst_plane2_size = dst_plane2_stride * dst_plane2_height;
int dst_frame_size = dst_plane1_size + dst_plane2_size;
uint8* dst_buffer_y = (unsigned char *)malloc(dst_frame_size);
uint8* dst_buffer_u = dst_buffer_y + dst_plane1_size;
uint8* dst_buffer_v = dst_buffer_u + dst_plane1_size / 4;
// 转换为const char*
const uint8 *src_img_data = (const uint8 *)[imgData bytes];
size_t src_img_size = [imgData length];
libyuv::MJPGToI420(/*const uint8* sample*/ src_img_data,
/*size_t sample_size*/ src_img_size,
/*uint8* dst_y*/ dst_buffer_y,
/*int dst_stride_y*/ dst_plane1_stride,
/*uint8* dst_u*/ dst_buffer_u,
/*int dst_stride_u*/ (int)dst_plane1_stride >> 1,
/*uint8* dst_v*/ dst_buffer_v,
/*int dst_stride_v*/ (int)dst_plane1_stride >> 1,
/*int src_width*/ img_width,
/*int src_height*/ img_height,
/*int dst_width*/ img_width,
/*int dst_height*/ img_height);
// static NSInteger count = 0;
// count++;
// if (count == 1) {
// NSData *dstData = [NSData dataWithBytes:dst_buffer_y length:dst_frame_size];
// NSString *dstPath = [NSString stringWithFormat:@"%@%@", NSHomeDirectory(), @"/Documents/png2i420.yuv"];
// if ([[NSFileManager defaultManager] fileExistsAtPath:dstPath]) {
// [[NSFileManager defaultManager] removeItemAtPath:dstPath error:nil];
// }
// [dstData writeToFile:dstPath atomically:NO];
// }
// 2.逆时针旋转90度
if (rotate) {
int rotate_plane1_stride = img_height;
if ((img_height % stride_length) != 0) {
rotate_plane1_stride = (img_height / stride_length + 1) * stride_length;
}
int rotate_plane2_stride = rotate_plane1_stride;
int rotate_plane1_height = img_width;
int rotate_plane2_height = img_width >> 1;
int rotate_plane1_size = rotate_plane1_stride * rotate_plane1_height;
int rotate_plane2_size = rotate_plane2_stride * rotate_plane2_height;
int rotate_frame_size = rotate_plane1_size + rotate_plane2_size;
uint8* rotate_buffer_y = (unsigned char *)malloc(rotate_frame_size);
uint8* rotate_buffer_u = rotate_buffer_y + rotate_plane1_size;
uint8* rotate_buffer_v = rotate_buffer_u + rotate_plane1_size / 4;
libyuv::I420Rotate(/*const uint8 *src_y*/ dst_buffer_y,
/*int src_stride_y*/ dst_plane1_stride,
/*const uint8 *src_u*/ dst_buffer_u,
/*int src_stride_u*/ dst_plane1_stride >> 1,
/*const uint8 *src_v*/ dst_buffer_v,
/*int src_stride_v*/ dst_plane1_stride >> 1,
/*uint8 *dst_y*/ rotate_buffer_y,
/*int dst_stride_y*/ rotate_plane1_stride,
/*uint8 *dst_u*/ rotate_buffer_u,
/*int dst_stride_u*/ rotate_plane1_stride >> 1,
/*uint8 *dst_v*/ rotate_buffer_v,
/*int dst_stride_v*/ rotate_plane1_stride >> 1,
/*int src_width*/ img_width,
/*int src_height*/ img_height,
/*enum RotationMode mode*/ libyuv::kRotate270
);
free(dst_buffer_y);
// static NSInteger count = 0;
// count++;
// if (count == 1) {
// NSData *dstData = [NSData dataWithBytes:scale_buffer_y length:scale_frame_size];
// NSString *dstPath = [NSString stringWithFormat:@"%@%@", NSHomeDirectory(), @"/Documents/i420_111.yuv"];
// if ([[NSFileManager defaultManager] fileExistsAtPath:dstPath]) {
// [[NSFileManager defaultManager] removeItemAtPath:dstPath error:nil];
// }
// [dstData writeToFile:dstPath atomically:NO];
// }
// 3.把旋转后的I420数据转换为NV12
int nv12_plane1_stride = rotate_plane1_stride;
int nv12_width = img_height;
int nv12_hight = img_width;
int nv12_frame_size = rotate_frame_size;
uint8 *nv12_dst_y = (uint8 *)malloc(nv12_frame_size);
uint8 *nv12_dst_uv = nv12_dst_y + nv12_plane1_stride * nv12_hight;
libyuv::I420ToNV12(/*const uint8 *src_y*/ rotate_buffer_y,
/*int src_stride_y*/ rotate_plane1_stride,
/*const uint8 *src_u*/ rotate_buffer_u,
/*int src_stride_u*/ rotate_plane1_stride >> 1,
/*const uint8 *src_v*/ rotate_buffer_v,
/*int src_stride_v*/ rotate_plane1_stride >> 1,
/*uint8 *dst_y*/ nv12_dst_y,
/*int dst_stride_y*/ nv12_plane1_stride,
/*uint8 *dst_uv*/ nv12_dst_uv,
/*int dst_stride_uv*/ nv12_plane1_stride,
/*int width*/ nv12_width,
/*int height*/ nv12_hight);
free(rotate_buffer_y);
// 4.NV12转换为CVPixelBufferRef
NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}};
CVPixelBufferRef dstPixelBuffer = NULL;
CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,
nv12_width, nv12_hight, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
(__bridge CFDictionaryRef)pixelAttributes, &dstPixelBuffer);
CVPixelBufferLockBaseAddress(dstPixelBuffer, 0);
uint8_t *yDstPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 0);
memcpy(yDstPlane, nv12_dst_y, nv12_plane1_stride * nv12_hight);
uint8_t *uvDstPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 1);
memcpy(uvDstPlane, nv12_dst_uv, nv12_plane1_stride * nv12_hight / 2);
if (result != kCVReturnSuccess) {
NSLog(@"Unable to create cvpixelbuffer %d", result);
}
CVPixelBufferUnlockBaseAddress(dstPixelBuffer, 0);
free(nv12_dst_y);
return dstPixelBuffer;
} else {
// 3.把I420数据转换为NV12
int nv12_plane1_stride = dst_plane1_stride;
int nv12_width = img_width;
int nv12_hight = img_height;
int nv12_frame_size = dst_frame_size;
uint8 *nv12_dst_y = (uint8 *)malloc(nv12_frame_size);
uint8 *nv12_dst_uv = nv12_dst_y + nv12_plane1_stride * nv12_hight;
libyuv::I420ToNV12(/*const uint8 *src_y*/ dst_buffer_y,
/*int src_stride_y*/ dst_plane1_stride,
/*const uint8 *src_u*/ dst_buffer_u,
/*int src_stride_u*/ dst_plane1_stride >> 1,
/*const uint8 *src_v*/ dst_buffer_v,
/*int src_stride_v*/ dst_plane1_stride >> 1,
/*uint8 *dst_y*/ nv12_dst_y,
/*int dst_stride_y*/ nv12_plane1_stride,
/*uint8 *dst_uv*/ nv12_dst_uv,
/*int dst_stride_uv*/ nv12_plane1_stride,
/*int width*/ nv12_width,
/*int height*/ nv12_hight);
free(dst_buffer_y);
// 4.NV12转换为CVPixelBufferRef
NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}};
CVPixelBufferRef dstPixelBuffer = NULL;
CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,
nv12_width, nv12_hight, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
(__bridge CFDictionaryRef)pixelAttributes, &dstPixelBuffer);
CVPixelBufferLockBaseAddress(dstPixelBuffer, 0);
uint8_t *yDstPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 0);
memcpy(yDstPlane, nv12_dst_y, nv12_plane1_stride * nv12_hight);
uint8_t *uvDstPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 1);
memcpy(uvDstPlane, nv12_dst_uv, nv12_plane1_stride * nv12_hight / 2);
if (result != kCVReturnSuccess) {
NSLog(@"Unable to create cvpixelbuffer %d", result);
}
CVPixelBufferUnlockBaseAddress(dstPixelBuffer, 0);
free(nv12_dst_y);
return dstPixelBuffer;
}
}