想在macOS平台上写一个将Mac摄像头上获取到的视频数据使用VideoToolBox编码后socket发送出去的服务器,但是遇到了好多问题
先是在接受数据的客户端最终渲染出来的视频中发现绿屏现象,如下图所示:
并且报-12911的错误信息,网上查了一下说是视频数据不完整的原因,
经过验证,初步排除了socket收发数据有误的可能,所以暂时将焦点放在了macOS端的工程上,
将macOS上采集到的视频进行VideoToolBox编码,编码后写入文件,存储成h.264文件,用VLC进行播放时,发现视频整体速度偏快
经过好一番折腾,终于发现有蹊跷的地方:AVCapture输出流代理
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
一般情况下,当在iOS环境下,默认情况下,为30 fps,意味着该函数每秒调用30次
但是在macOS端的工程上,每秒的调用并没有达到30次,有时候只有15次,有时候又是20次…
猜想正是因为这样,而编码器以为每秒还是有30帧,所以VLC进行播放时,走了30帧,以为是1秒,但是实际上不止有1秒,因此播放时会有快进的感觉。
在代理中简单打印摄像头输出数据的信息:
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CMVideoFormatDescriptionRef desc = NULL; CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &desc); CFDictionaryRef extensions = CMFormatDescriptionGetExtensions(desc); NSLog(@"extensions = %@", extensions);输出:
extensions = { CVBytesPerRow = 1924; CVImageBufferColorPrimaries = "ITU_R_709_2"; CVImageBufferTransferFunction = "ITU_R_709_2"; CVImageBufferYCbCrMatrix = "ITU_R_709_2"; // ITU_R_709_2是HD视频的方案,一般用于YUV422,YUV至RGB的转换矩阵和SD视频(一般是ITU_R_601_4)并不相同。 Version = 2; }即便是在把VideoToolBox 设置成kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,输出也同上,
而在iOS真机上输出的是:
extensions = { CVBytesPerRow = 724; CVImageBufferChromaLocationTopField = Center; CVImageBufferColorPrimaries = "ITU_R_709_2"; CVImageBufferTransferFunction = "ITU_R_709_2"; CVImageBufferYCbCrMatrix = "ITU_R_601_4"; Version = 2; }
这里暂时只比较CVImageBufferYCbCrMatrix不同,根据查得的资料(链接),可知,ITU_R_709_2是HD视频的方案,一般用于YUV422,说明macOS摄像头摄像头采集到的是YUV422即YUYV格式的视频,
又将AVCaptureDevice的formats打印出来:
// 获取当前设备支持的像素格式 NSLog(@"-- videoDevice.formats = %@", videoDevice.formats);输出:
-- videoDevice.formats = ( "<AVCaptureDeviceFormat: 0x618000000c50> 'vide'/'yuvs' enc dims = 1280x720, pres dims = 1280x720 { 1.00-30.00 fps }", "<AVCaptureDeviceFormat: 0x618000000cf0> 'vide'/'2vuy' enc dims = 1280x720, pres dims = 1280x720 { 1.00-30.00 fps }", "<AVCaptureDeviceFormat: 0x618000000d20> 'vide'/'420v' enc dims = 1280x720, pres dims = 1280x720 { 1.00-30.00 fps }", "<AVCaptureDeviceFormat: 0x618000000d50> 'vide'/'yuvs' enc dims = 640x480, pres dims = 640x480 { 1.00-30.00 fps }", "<AVCaptureDeviceFormat: 0x618000000d80> 'vide'/'2vuy' enc dims = 640x480, pres dims = 640x480 { 1.00-30.00 fps }", "<AVCaptureDeviceFormat: 0x618000000db0> 'vide'/'420v' enc dims = 640x480, pres dims = 640x480 { 1.00-30.00 fps }", "<AVCaptureDeviceFormat: 0x618000000de0> 'vide'/'yuvs' enc dims = 320x240, pres dims = 320x240 { 1.00-30.00 fps }", "<AVCaptureDeviceFormat: 0x618000000e10> 'vide'/'2vuy' enc dims = 320x240, pres dims = 320x240 { 1.00-30.00 fps }", "<AVCaptureDeviceFormat: 0x618000000e40> 'vide'/'42(可以看到打印的信息并不完整,不知道是为什么,当打印较长的其他数据如NSData时,也有打印不完整的现象)
由如上的输出信息可知此台Mac的摄像头支持的格式为:yuvs,2vuy,420v,
那么将VideoToolBox的编码类型改成:kCVPixelFormatType_422YpCbCr8_yuvs,或者kCVPixelFormatType_422YpCbCr8
但是当再次编译运行时,实际上AVCapture的输出流代理仍然没有达到每秒调用30次…
刚刚查到可以将YUV422转成YUV420,再进行编码,但是这样依然不能改变AVCapture输出流代理调用的次数啊…
这个方法待验证
========================== 8.12 更新 =============================
(呃今天把电脑换了个位置,Mac摄像头的帧率就达到30次了,我也是醉醉的,之前测试时的环境比较暗,难道是跟环境有关?)
MacBook摄像头参数为: 720p FaceTime HD摄像头
在关于在apple关于图像pixelformat以及YUV等相关知识可以去看看这篇文章:
========================== 9.19 更新 =============================
好久没有更新了,第一次在macOS上写程序 遇到了好多棘手的问题,自信都磨没了,最主要不在于有多难,而是资料太难找了有没有,想说巧妇难为无米之炊啊,甚至连FFmpeg编解码的资料都查了,也有想过干脆用Qt好了,网上资料一搜一大把。
后来想想还是不要太为难自己了,毕竟术业有专攻,别啥都看一下 最后啥也不行。
郁闷好久之后还是回到用iOS平台,写了个使用iPhone作为设备采集数据的工具,结果还算顺利。
iOS版的地址在这里:https://github.com/AmoAmoAmo/Smart_Device_Server
言归正传,macOS平台上的,就用了上面提到的,将YUV422转成YUV420,再进行编码。
做法如下:
1. CMSampleBufferRef 中提取yuv数据(Byte)
2. 处理yuv数据
3. yuv数据 转CVPixelBufferRef ,继续进行编码
代码如下:
// ========== 处理YUV422数据 ========== /* 1. CMSampleBufferRef 中提取yuv数据(Byte) 2. 处理yuv数据 3. yuv数据 转CVPixelBufferRef ,继续进行编码 */ -(CVPixelBufferRef)processYUV422ToYUV420WithSampleBuffer:(CMSampleBufferRef)sampleBuffer { // 1. 从CMSampleBufferRef中提取yuv数据 // 获取yuv数据 // 通过CMSampleBufferGetImageBuffer方法,获得CVImageBufferRef。 // 这里面就包含了yuv420数据的指针 CVImageBufferRef pixelBuffer_Before = CMSampleBufferGetImageBuffer(sampleBuffer); //表示开始操作数据 CVPixelBufferLockBaseAddress(pixelBuffer_Before, 0); //图像宽度(像素) size_t pixelWidth = CVPixelBufferGetWidth(pixelBuffer_Before); //图像高度(像素) size_t pixelHeight = CVPixelBufferGetHeight(pixelBuffer_Before); //yuv中的y所占字节数 size_t y_size = pixelWidth * pixelHeight; // 2. yuv中的u和v分别所占的字节数 size_t uv_size = y_size / 4; uint8_t *yuv_frame = malloc(uv_size * 2 + y_size); //获取CVImageBufferRef中的y数据 uint8_t *y_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer_Before, 0); memcpy(yuv_frame, y_frame, y_size); //获取CMVImageBufferRef中的uv数据 uint8_t *uv_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer_Before, 1); memcpy(yuv_frame + y_size, uv_frame, uv_size * 2); CVPixelBufferUnlockBaseAddress(pixelBuffer_Before, 0); NSData *yuvData = [NSData dataWithBytesNoCopy:yuv_frame length:y_size + uv_size * 2]; // 3. yuv 变成 转CVPixelBufferRef //现在要把NV12数据放入 CVPixelBufferRef中,因为 硬编码主要调用VTCompressionSessionEncodeFrame函数,此函数不接受yuv数据,但是接受CVPixelBufferRef类型。 CVPixelBufferRef pixelBuf_After = NULL; //初始化pixelBuf,数据类型是kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,此类型数据格式同NV12格式相同。 CVPixelBufferCreate(NULL, pixelWidth, pixelHeight, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBuf_After); // Lock address,锁定数据,应该是多线程防止重入操作。 if(CVPixelBufferLockBaseAddress(pixelBuf_After, 0) != kCVReturnSuccess){ NSLog(@"encode video lock base address failed"); return NULL; } //将yuv数据填充到CVPixelBufferRef中 uint8_t *yuv_frame_2 = (uint8_t *)yuvData.bytes; //处理y frame uint8_t *y_frame_2 = CVPixelBufferGetBaseAddressOfPlane(pixelBuf_After, 0); memcpy(y_frame_2, yuv_frame_2, y_size); uint8_t *uv_frame_2 = CVPixelBufferGetBaseAddressOfPlane(pixelBuf_After, 1); memcpy(uv_frame_2, yuv_frame_2 + y_size, uv_size * 2); CVPixelBufferUnlockBaseAddress(pixelBuf_After, 0); return pixelBuf_After; }
在代理didOutputSampleBuffer里,直接把获取到的原始数据sampleBuffer传给上面的函数,再把函数返回的CVPixelBufferRef传给VideoToolbox编码。
问题解决是解决了,但毕竟不是用更底层的代码转换的,效率低速度慢是慢了点,最终的效果还不错:
在client端收到视频时明显延时了好久,但是效果是达到了。万里长城也不是一天建成的,以后再慢慢积累吧。
以上源代码地址:https://github.com/AmoAmoAmo/Server_Mac
如果我的代码有帮助到你,请给我颗星星✨,比心。