随着基础设施的更新和扩容,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的视频,在处理过程中要尽量减少视频数据的拷贝,最好是零拷贝,这样才能将性能提升至最优。