随着基础设施的更新和扩容,4K信号在各个垂直领域的应用日渐增多和普及。由于4K视频的数据量是高清(1920x1080)信号的4倍,软件开发人员在处理视频的渲染和编码时面临新的挑战。近年来显卡技术的快速发展,使之成为开发人员实现视频编码的首选工具。如果4K@60Hz信号能被主机的GPU处理,这将充分释放CPU的运算负担,提高产品的稳定性,并节省整体成本。本文将重点介绍使用美乐威MWCapture SDK,利用Mac电脑GPU完成一路4K@60Hz信号的采集、渲染及编码。
MWCapture SDK是美乐威针对I/O系列提供的采集、编码、渲染等功能相关的开发接口和例程,能够帮助开发人员快速优化应用程序和算法。其软件开发库和例程包括用于改进应用程序开发和性能优化的工具,提供了美乐威自定义接口,使您能够将采集设备的特定功能(例如音视频采集、获取输入信号信息和设置信号源等)整合到软件中。您可以直接使用这些应用程序,也可以使用随附的例程来构建自己的应用程序。采集卡的型号、硬件配置、固件版本和驱动程序决定了可用的功能。
根据您所使用的采集设备的兼容性,库函数分为以下几类:
压缩格式为H.264时推荐:
Mac mini(2018) iMac(2019)
CPU:Quad-Core Intel Core i5 CPU:6-Core Intel Core i3
Memory:2GB Memory:8GB
GPU:Intel UHD Graphics 630 1536MB GPU:AMD Radeon Pro 570X
压缩格式是H.265时推荐:
iMac Pro(2019)
CPU:Intel Xeon 8 Core Processor
Memory:32GB
GPU:AMD Radeon Vega 56
我们建议使用美乐威SDK中的AVCapture例程进行测试。此例程经过美乐威仔细调试,可以充分发挥显卡的性能,实现目标功能。
开发环境:
MacOS:10.11及以以上版本
Xcode:与安装macOS系统相匹配
视频采集部分通过调用美乐威私有采集接口实现,主要步骤如下:
注意:美乐威SDK中提供的采集接口在不同平台上(Windows、Linux、macOS)上是一致的,因此在AVCapture例程中关于视频采集部分的代码可以移植搭配其他平台使用;但本文中的提到的视频渲染和编码接口使用了macOS平台相关接口,无法兼容Windows和Linux平台。
采集一帧视频数据。主要代码片段如下:
while (self.running) {
llExpireTime = llExpireTime + dwFrameDuration;
LONGLONG llCurrentTime = 0LL;
xr = MWGetDeviceTime(self.hChannel, &llCurrentTime);
if (xr != MW_SUCCEEDED) {
llExpireTime = 0LL; usleep(10000);
continue;
}
if (llExpireTime < llCurrentTime) {
llExpireTime = llCurrentTime;
}
xr = MWScheduleTimer(self.hChannel, hTimerNotify, llExpireTime);
if (xr != MW_SUCCEEDED) {
llExpireTime = llCurrentTime;
continue;
}
DWORD dwRet = MWWaitEvent(hTimerEvent, 1000);
if (dwRet <= 0) {
continue;
}
........
if (frame->pixelBuffer) {
........
xr = MWCaptureVideoFrameToVirtualAddressEx(self.hChannel,
MWCAP_VIDEO_FRAME_ID_NEWEST_BUFFERED,
byBuffer,
dwFrameSize,
cbStride,
FALSE,
(MWCAP_PTR64)pixelBuffer,
self.fourcc,
self.width,
self.height,
0,
0,
NULL,
NULL,
0,
100,
0,
100,
0,
MWCAP_VIDEO_DEINTERLACE_BLEND,
MWCAP_VIDEO_ASPECT_RATIO_IGNORE,
&rcSrc,
NULL,
0,
0,
MWCAP_VIDEO_COLOR_FORMAT_UNKNOWN,
MWCAP_VIDEO_QUANTIZATION_UNKNOWN,
MWCAP_VIDEO_SATURATION_UNKNOWN);
MWWaitEvent(hCaptureEvent, -1);
CVPixelBufferUnlockBaseAddress(frame->pixelBuffer, 0);
........
}
}
........
} while (FALSE);
AVCapture 例程对视频渲染进行了优化。首先将视频数据的指针封装为CMSampleBufferRef类,然后将这个类直接交给渲染器进行渲染。CMSampleBufferRef类只是对原始视频数据指针的引用,因此这个过程中不会有任何的数据拷贝。
if (self.viewEnable) {
CMSampleTimingInfo timing = kCMTimeInvalid, kCMTimeInvalid, kCMTimeInvalid};
CMVideoFormatDescriptionRef videoInfo = NULL;
CVReturn result = CMVideoFormatDescriptionCreateForImageBuffer(NULL, frame->pixelBuffer, &videoInfo);
CMSampleBufferRef sampleBuffer = NULL;
result = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, frame->pixelBuffer, true, NULL, NULL, videoInfo, &timing, &sampleBuffer);
CFRelease(videoInfo);
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
if(self.videoLayer) {
[self.videoLayer enqueueSampleBuffer:(CMSampleBufferRef)sampleBuffer];
}
CFRelease(sampleBuffer);
}
AVCapture针对视频编码同样进行了优化。首先视频编码的运算量较大,重新开启了一个线程,专门用于视频编码。
if (self.audioCaptureThreadId == 0) {
pthread_t tid = 0;
if (0 == pthread_create(&tid, NULL, onVideoEncodeThreadProc,(__bridge void*)(self))) {
self.audioCaptureThreadId = tid;
}
}
其次,在采集了一帧数据后,会将数据指针放进一个队列。在编码线程中,会从队列中获取数据指针,然后将数据指针送到编码器进行编码。因为数据都是以指针的形式进行传输的,因此传输过程中,也是没有任何的数据拷贝的。
[self.vtEncLock lock];
if (self.vtEnc) {
((std::queue > *)self.encPixelFrameQueue)->push(frame);
while(((std::queue > *)self.encPixelFrameQueue)->size() > MAX_VIDEO_ENCODE_BUFFER_FRAMES) {
((std::queue > *)self.encPixelFrameQueue)->pop();
}
}
[self.vtEncLock unlock];
std::queue > *encQueue = (std::queue > *)self.encPixelFrameQueue;
while (self.encoding) {
std::shared_ptr frame;
[self.vtEncLock lock];
if (!encQueue->empty()) {
frame = encQueue->front();
}
[self.vtEncLock unlock];
if (frame != NULL && frame->pixelBuffer) {
if (self.vtEnc) {
//printf("put video frame:%lld\n", frame->timestamp);
mw_venc_put_imagebuffer(self.vtEnc, frame->pixelBuffer, frame->timestamp);
}
[self.vtEncLock lock];
encQueue->pop();
[self.vtEncLock unlock];
........
} else {
usleep(5000);
}
}
综上所述,要保证能够同时预览和录制4K@60Hz的视频,在处理过程中要尽量减少视频数据的拷贝,最好是零拷贝,这样才能将性能提升至最优。