使用vImageRotate90_ARGB8888旋转CMSampleBuffer时崩溃求助
我仔细排查了你的代码,发现几个关键问题导致了崩溃,下面逐一说明并给出修复方案:
1. 像素格式不匹配
vImageRotate90_ARGB8888 仅支持 kCVPixelFormatType_32ARGB 格式的像素缓冲区,但你的代码直接复用了原 CVPixelBuffer 的像素格式。如果原采样缓冲区的格式不是32ARGB(比如常见的kCVPixelFormatType_420YpCbCr8BiPlanarFullRange),调用这个函数必然会崩溃。
修复:先检查原像素格式,若不是32ARGB,需要先转换格式;如果你的采集流程可以直接设置为32ARGB格式,那是最优解(比如在AVCaptureSession配置时指定)。
2. 目标缓冲区参数错误
旋转90度顺时针后,输出缓冲区的宽高应该和原缓冲区交换:
- 原宽度
width→ 目标高度 - 原高度
height→ 目标宽度
同时,目标缓冲区的bytesPerRow计算错误,ARGB8888每个像素占4字节,所以正确的行字节数应该是 4 * 目标宽度(也就是4 * height)。你之前的计算多乘了sizeof(unsigned char),这是冗余的(因为unsigned char本身就是1字节)。
3. 内存分配大小错误
你用bytesPerRow * height计算内存大小,但bytesPerRow可能因为内存对齐比4 * width大,而旋转后的缓冲区实际需要的内存是4 * 目标宽度 * 目标高度(也就是4 * height * width),用原行字节数计算会分配多余的内存,甚至可能因为对齐问题导致vImage处理出错。
4. 旋转参数定义问题
确认kRotate90DegreesClockwise的宏定义是否正确:vImageRotate90_ARGB8888的rotation参数接受0-3的整数,分别对应0°、90°、180°、270°顺时针旋转。如果你的宏定义是90而不是1,这会导致vImage读取越界,直接崩溃。
修正后的完整代码
typedef NS_ENUM(uint8_t, vImageRotation) { kRotate0DegreesClockwise = 0, kRotate90DegreesClockwise = 1, kRotate180DegreesClockwise = 2, kRotate270DegreesClockwise = 3 }; - (CVPixelBufferRef)rotateBuffer:(CMSampleBufferRef)sampleBuffer { CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); if (!pixelBuffer) return NULL; // 锁定缓冲区基地址 CVReturn lockStatus = CVPixelBufferLockBaseAddress(pixelBuffer, 0); if (lockStatus != kCVReturnSuccess) { NSLog(@"Failed to lock pixel buffer"); return NULL; } size_t originalWidth = CVPixelBufferGetWidth(pixelBuffer); size_t originalHeight = CVPixelBufferGetHeight(pixelBuffer); OSType originalFormat = CVPixelBufferGetPixelFormatType(pixelBuffer); // 确保像素格式是ARGB8888,否则需要先转换 if (originalFormat != kCVPixelFormatType_32ARGB) { NSLog(@"Unsupported pixel format for vImageRotate90_ARGB8888"); CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); // 这里可以添加格式转换逻辑,比如使用CIContext转换 return NULL; } void *sourceBaseAddr = CVPixelBufferGetBaseAddress(pixelBuffer); size_t sourceBytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); // 旋转90度后,宽高交换 size_t destWidth = originalHeight; size_t destHeight = originalWidth; // ARGB8888每个像素4字节,行字节数按目标宽度计算 size_t destBytesPerRow = 4 * destWidth; // 计算需要分配的内存大小 size_t destBufferSize = destBytesPerRow * destHeight; unsigned char *destPixelData = (unsigned char *)malloc(destBufferSize); if (!destPixelData) { NSLog(@"Failed to allocate memory for destination buffer"); CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); return NULL; } // 初始化vImage缓冲区 vImage_Buffer sourceBuffer = {sourceBaseAddr, originalHeight, originalWidth, sourceBytesPerRow}; vImage_Buffer destinationBuffer = {destPixelData, destHeight, destWidth, destBytesPerRow}; Pixel_8888 bgColor = {0, 0, 0, 0}; // 使用正确的旋转参数(1代表90度顺时针) vImage_Error error = vImageRotate90_ARGB8888(&sourceBuffer, &destinationBuffer, kRotate90DegreesClockwise, bgColor, kvImageNoFlags); if (error != kvImageNoError) { NSLog(@"vImageRotate failed with error: %ld", error); free(destPixelData); CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); return NULL; } // 创建旋转后的CVPixelBuffer,注意指定正确的像素格式 CVPixelBufferRef rotatedBuffer = NULL; CVReturn createStatus = CVPixelBufferCreateWithBytes(kCFAllocatorDefault, destWidth, destHeight, kCVPixelFormatType_32ARGB, destPixelData, destBytesPerRow, freePixelBufferData, NULL, NULL, &rotatedBuffer); if (createStatus != kCVReturnSuccess) { NSLog(@"Failed to create rotated pixel buffer"); free(destPixelData); rotatedBuffer = NULL; } CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); return rotatedBuffer; } void freePixelBufferData(void *releaseRefCon, const void *baseAddress) { free((void *)baseAddress); }
额外建议
- 如果你的原缓冲区格式不是32ARGB,推荐使用
CIContext先转换格式,或者在AVCaptureDeviceInput配置时直接设置输出格式为kCVPixelFormatType_32ARGB,这样可以避免额外的格式转换开销。 - 每次调用vImage函数后都检查错误码,这样能更快定位问题,而不是直接崩溃。
- 确保所有内存分配都有对应的释放逻辑,避免内存泄漏。
内容的提问来源于stack exchange,提问作者anthonya1999

