[置顶] VideoToolbox视频编码——在macOS上对获取到的视频进行编码的问题记录 及YUV422转YUV420

标签: macOS 视频编码 视频直播 h264 YUV422转YUV420
954人阅读 评论(1) 收藏 举报
分类:

想在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


如果我的代码有帮助到你,请给我颗星星✨,比心。







查看评论

avsubtitleWriter demo解析(二):创建CMSampleBufferRef

这里有必要要阐述下字幕wen'ji
  • Mamong
  • Mamong
  • 2014-04-13 07:57:21
  • 7477

YUV格式

http://blog.csdn.net/airk000/article/details/25032901 http://linuxtv.org/downloads/v4l-...
  • u011270282
  • u011270282
  • 2016-02-19 11:13:46
  • 1820

iOS获取camera的yuv数据

首先要设置输出格式:  [videoOut setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:k...
  • u011270282
  • u011270282
  • 2016-02-23 19:25:23
  • 4242

VideoToolBox编码h264

创建VTCompressionSession   aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dis...
  • itiapp_home
  • itiapp_home
  • 2016-09-28 14:06:34
  • 1009

GPUImage拍摄视频第一帧黑屏问题

使用GPUImage录制视频时第一帧会出现黑屏或者白屏,并且调用addAudioInputsAndOutputs也不好使 此时需要修改GPUImageMovieWriter.m的源码,在其中添加以下代...
  • yyjjyysleep
  • yyjjyysleep
  • 2017-04-24 11:28:34
  • 1787

YUV420与YUV422区别

YUV420与YUV422区别   Y:亮度分量    UV:色度分量        Y与RGB的演算关系为:Y = 0.2126 R + 0.7152 G + 0.0722 B   YU...
  • sanmaoljh
  • sanmaoljh
  • 2017-08-26 11:44:21
  • 1705

将视频 YUV 格式编码成 H264

首先开始的时候我们插入一张雷神大大的图帮助大家理解一下我们今天的操作究竟属于那一步。 音视频格式封装层次 从上图可以看出我们要做的,就是将像素层的 YUV 格式,编码出编码层的 h264数据。 ...
  • ZH952016281
  • ZH952016281
  • 2016-09-23 18:11:43
  • 3853

ffmpeg学习八:软件生成yuv420p视频并将其编码为H264格式

通过前面对ffmpeg中常用的几个api的源码分析,从而对api有了更好的理解。之前已经做过视频的解码了,今天来尝试视频的编码。ffmpeg已经给我们提供了相应的可供参考的程序:doc/example...
  • u011913612
  • u011913612
  • 2016-12-16 09:26:18
  • 1921

几种常见的YUV格式--yuv422:yuv420

关于yuv 格式 YUV 格式通常有两大类:打包(packed)格式和平面(planar)格式。前者将 YUV 分量存放在同一个数组中, 通常是几个相邻的像素组成一个宏像素(macro-pix...
  • u012288815
  • u012288815
  • 2016-07-01 10:50:53
  • 6823
    个人资料
    持之以恒
    等级:
    访问量: 1万+
    积分: 652
    排名: 7万+
    最新评论