macos中的YUV420p内存布局...

4 篇文章 0 订阅

前言

最近在这折腾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通道每一行字节数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值