前言
最近在这折腾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通道每一行字节数 |