macos中的YUV420p内存布局...

来源:互联网 发布:红鸟房卡手机棋牌源码 编辑:程序博客网 时间:2024/06/06 00:04

    • 前言
    • 旧的方法
    • 新的方法
      • 我的yuv420p去哪了
      • 显示的有问题
        • 验证方式
        • 怎么解决内存对齐的问题
    • 总结

前言

最近在这折腾mac截屏。 中间遇到了一些坎坷特此记录一下。

旧的方法

之前经验不够,网上什么方法简单,就用什么。 因为项目是使用Qt编的,Qt也做了一些与ObjC类的一些兼容,就采取了如下方式:

QPixmap OsxFun::getMainScreenPixmap(){    int activeDisplay;    size_t screenX, screenY;    activeDisplay = CGMainDisplayID();    screenY = CGDisplayPixelsHigh(activeDisplay);    screenX = CGDisplayPixelsWide(activeDisplay);    qDebug()<<"robin:img cr befor time:"<<QDateTime::currentDateTime().toString("mm:ss:zzz")<<"-----------";    CGImageRef image = CGDisplayCreateImage(activeDisplay);    qDebug()<<"robin:cover befort time:"<<QDateTime::currentDateTime().toString("mm:ss:zzz");    QPixmap t_outPixmap = QtMac::fromCGImageRef(image);    qDebug()<<"robin:cover    end time:"<<QDateTime::currentDateTime().toString("mm:ss:zzz");    if(m_bIsRetina)    {        t_outPixmap.setDevicePixelRatio(2);    }    CGImageRelease(image);    return t_outPixmap;}

但是效率实在不怎么样。 CGDisplayCreateImage 花去了几十ms。 QtMac::fromCGImageRef 又花去了几十ms。 两个加起来在60ms~110ms之间, 我的MBP,1s中只有10帧的样子。而且发现一个问题,当我当前程序的窗口切换为其它程序的时候,系统自动将我这个程序的cpu占用率降低了,结果就进一步降低了截屏的帧数。 于是继续找新的方式。

新的方法

我主要参考的是《是怎么踩过在 OSX 上录屏的坑的》这篇文章,他的demo写的很不错,为作者点个赞。

我的yuv420p去哪了

在设置输出格式那里,先前的作者用的是kCVPixelFormatType_32BGRA,而我需要的是yuv420p。结果发现了mac不按套路出牌的地方(或者说是跟ffmpeg没商量好的地方)。
在这里我找到了对应

static const struct AVFPixelFormatSpec avf_pixel_formats[] = {{ AV_PIX_FMT_MONOBLACK,    kCVPixelFormatType_1Monochrome },{ AV_PIX_FMT_RGB555BE,     kCVPixelFormatType_16BE555 },{ AV_PIX_FMT_RGB555LE,     kCVPixelFormatType_16LE555 },{ AV_PIX_FMT_RGB565BE,     kCVPixelFormatType_16BE565 },{ AV_PIX_FMT_RGB565LE,     kCVPixelFormatType_16LE565 },{ AV_PIX_FMT_RGB24,        kCVPixelFormatType_24RGB },{ AV_PIX_FMT_BGR24,        kCVPixelFormatType_24BGR },{ AV_PIX_FMT_0RGB,         kCVPixelFormatType_32ARGB },{ AV_PIX_FMT_BGR0,         kCVPixelFormatType_32BGRA },{ AV_PIX_FMT_0BGR,         kCVPixelFormatType_32ABGR },{ AV_PIX_FMT_RGB0,         kCVPixelFormatType_32RGBA },{ AV_PIX_FMT_BGR48BE,      kCVPixelFormatType_48RGB },{ AV_PIX_FMT_UYVY422,      kCVPixelFormatType_422YpCbCr8 },{ AV_PIX_FMT_YUVA444P,     kCVPixelFormatType_4444YpCbCrA8R },{ AV_PIX_FMT_YUVA444P16LE, kCVPixelFormatType_4444AYpCbCr16 },{ AV_PIX_FMT_YUV444P,      kCVPixelFormatType_444YpCbCr8 },{ AV_PIX_FMT_YUV422P16,    kCVPixelFormatType_422YpCbCr16 },{ AV_PIX_FMT_YUV422P10,    kCVPixelFormatType_422YpCbCr10 },{ AV_PIX_FMT_YUV444P10,    kCVPixelFormatType_444YpCbCr10 },{ AV_PIX_FMT_YUV420P,      kCVPixelFormatType_420YpCbCr8Planar },{ AV_PIX_FMT_NV12,         kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange },{ AV_PIX_FMT_YUYV422,      kCVPixelFormatType_422YpCbCr8_yuvs },#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080{ AV_PIX_FMT_GRAY8,        kCVPixelFormatType_OneComponent8 },#endif{ AV_PIX_FMT_NONE, 0 }};

熟悉的yuv420p化为了kCVPixelFormatType_420YpCbCr8Planar。 我英文不好,从注释上我看不出来它俩一样。

//mac文档中的说明kCVPixelFormatType_420YpCbCr8Planar = 'y420',   /* Planar Component Y'CbCr 8-bit 4:2:0.  baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrPlanar struct *///ffmpeg文档中的说明 AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)

按理说故事结束了……

显示的有问题

验证方式

将产生的yuv数据通过保存到字符串中,然后,写入文件中,然后用yuv播放器播放它,看看是否正常。

size_t width = CVPixelBufferGetWidth(imageBuffer);size_t height = CVPixelBufferGetHeight(imageBuffer);uint8_t *yBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);size_t yPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);uint8_t *uBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);size_t uPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);uint8_t *vBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 2);size_t vPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 2);size_t t_planeCount = CVPixelBufferGetPlaneCount(imageBuffer);NSMutableData *t_willWriteData = [[NSMutableData alloc] init];[t_willWriteData appendBytes:yBuffer length:yPitch*height];[t_willWriteData appendBytes:uBuffer length:uPitch*height/2];[t_willWriteData appendBytes:vBuffer length:vPitch*height/2];

这样就将yuv数据保存到文件中。 然后用yuv播放器查看的时候,发现图像不对。y通道是对的,u通道、v通道不对。

注:我的屏幕分辨率是1440*900 按道理
yPitch 应该等于1440(为什么是应该呢,因为我遇到过 1366成为1376的情况)
uPitch 和 vPitch 应该等于yPitch的一半(720)。
谁知uPitch和vPitch的值为736。
我就有点懵逼了。
我尝试将数据的存入方式改为了

NSMutableData *t_willWriteData = [[NSMutableData alloc] init];[t_willWriteData appendBytes:yBuffer length:yPitch*height];for (int i = 0; i < height; i++) {    [t_willWriteData appendBytes:uBuffer+i*uPitch length:yPitch/2];}for (int i = 0; i < height; i++) {    [t_willWriteData appendBytes:vBuffer+i*uPitch length:yPitch/2];}

现在显示正常了。

现在确定了确实是内存对齐的问题。

怎么解决内存对齐的问题

像上面一样,写两个for循环,确实可以解决问题。但是又不能用for循环。
因为视频处理中用for循环处理像素数据,最后程序效率肯定不咋滴。

翻了很久的文档,没找打方式。又思考了良久(大半天时间),意识到了解决办法(还没验证) 从播放器端解决。播放器显示处理的时候,让它来处理这个对齐问题。

总结

想不到YUV420p的内存对齐还有这么多的问题。 不光是Y通道的大小不一定等于图片宽带,UV的大小也不一定等于Y的一半。

//来自ffmepg头文件/** * For video, size in bytes of each picture line. * For audio, size in bytes of each plane. * * For audio, only linesize[0] may be set. For planar audio, each channel * plane must be the same size. * * For video the linesizes should be multiples of the CPUs alignment * preference, this is 16 or 32 for modern desktop CPUs. * Some code requires such alignment other code can be slower without * correct alignment, for yet other it makes no difference. * * @note The linesize may be larger than the size of usable data -- there * may be extra padding present for performance reasons. */int AVFrame::linesize[AV_NUM_DATA_POINTERS];

从目前来看的情况是:

通道 关系 图片 Y通道每一行字节数 >= 图片宽度 U通道每一行字节数 >= Y通道每一行字节数 U通道每一行字节数 = V通道每一行字节数

YUV420p实际有效的数据量:

通道 关系 图片 Y通道每一行字节数 = 图片宽度 U通道每一行字节数 = Y通道每一行字节数 U通道每一行字节数 = V通道每一行字节数
原创粉丝点击