IOS编码的注意点
最近在学习IOS用videotoolbox
框架来编码,遇到了一些问题,总结一下。
1.尝试编码纯NV12数据时遇到调用VTCompressionSessionEncodeFrame
函数后,回调返回 kVTPixelTransferNotSupportedErr
错误,错误码是-12905
.
首先说明一下NV12
格式数据的来源是先通过摄像头采集数据,然后将YUV
的数据保存到文件里,采集到的数据时NV12
格式的,然后将数据直接用videotoolbox
来直接编码成h264
流。因为编码时接收的是CVPixelBufferRef
格式的数据,所以需要先将YUV
数据封装到CVPixelBufferRef
格式中,网上找了很多方法,可以通过调用CVPixelBufferCreateWithPlanarBytes
来创建,但是编码的时候就是会报-12905
这个错。后来在ffmpeg
框架里面找到了videotooboxenc.c
里面有将AVFrame
里面的YUV
数据转换成CVPixelBufferRef
格式,然后用于编码。代码如下:
CVReturn status = 0;
CVPixelBufferPoolRef pix_buf_pool = VTCompressionSessionGetPixelBufferPool(_encodingSession);
CVPixelBufferRef pix_buffer = nil;
if (!pix_buf_pool) {
NSLog(@"create pix_buf_pool error");
}
status = CVPixelBufferPoolCreatePixelBuffer(NULL,
pix_buf_pool,
&pix_buffer);
if (status != kCVReturnSuccess) {
NSLog(@"CVPixelBufferPoolCreatePixelBuffer error status:%d",status);
}
status = CVPixelBufferLockBaseAddress(pix_buffer, 0);
if (status) {
NSLog(@"CVPixelBufferLockBaseAddress error");
}
if (CVPixelBufferIsPlanar(pix_buffer)) {
uint8_t *dst_addr = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pix_buffer, 0);
uint8_t *src_addr = data;
size_t width = CVPixelBufferGetWidth(pix_buffer);
size_t yBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pix_buffer, 0);
size_t yPlaneHeight = CVPixelBufferGetHeightOfPlane(pix_buffer, 0);
for (int i=0; i<yPlaneHeight; ++i) {
memcpy(dst_addr, src_addr, width);
src_addr += width;
dst_addr += yBytesPerRow;
}
dst_addr = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pix_buffer, 1);
size_t uvBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pix_buffer, 1);
size_t uvPlaneHeight = CVPixelBufferGetHeightOfPlane(pix_buffer, 1);
for (int i=0; i<uvPlaneHeight; ++i) {
memcpy(dst_addr, src_addr, width);
dst_addr+= uvBytesPerRow;
src_addr += width;
}
}
status = CVPixelBufferUnlockBaseAddress(pix_buffer, 0);
if (pix_buffer) {
[self pushData:(__bridge NSData *)(pix_buffer) metadata:metadata];
}
CFRelease(pix_buffer);
这样就可以了,这里的pix_buffer
是通过编码器创建的VTCompressionSessionRef
类型的_encodingSession
获取CVPixelBufferPoolRef
, 然后通过Pool创建出pix_buffer
才能把数据给到编码器。但这样有个问题,就是不能随便变更编码的分辨率。只能以创建编码器时设定的分辨率进行编码,当前的解决方案是,通过ffmpeg
将yuv的数据调整到目标分辨率后,进行编码。
2.baseline profile模式下的一些坑
当profile是AVVideoProfileLevelH264BaselineAutoLevel
时,如果设置了kVTCompressionPropertyKey_DataRateLimits
并且设置了kVTH264EntropyMode_CABAC
熵编码模式,那编出来的视频将非常模糊。可能本h264的baseline本身是不支持CABAC
的编码模式的,搞不懂VideotoolBox这个框架这个参数为啥会有影响。
3.high profile模式下B帧的坑
当在high
模式下,按API文档来说设置了kVTCompressionPropertyKey_AllowFrameReordering
为true,就可以额编码出B帧了,但如果同时设置了kVTCompressionPropertyKey_DataRateLimits
那么B帧这个设置就会失效。
4.kVTCompressionPropertyKey_DataRateLimits 这个参数的坑
最近发现kVTCompressionPropertyKey_DataRateLimits
这个参数对机型和系统很敏感, 在iPhone6 plus机型+ iOS8.1和iOS8.2系统上如果是在初始化的时候设置,会导致VTCompressionSessionPrepareToEncodeFrames
这个方法的调用的失败,导致编码出错。
并且这个参数比较坑,如果初始化的时候不设置这个参数,后面再设置会无效,初始化设置了,后面再修改这个参数,是有效的。
未完待续