webrtc中视频采集实现分析(一) 采集及图像处理接口封装


视频采集是媒体库最基础功能。但是它的实现与操作系统有强相关性,因为不同的操作系统提供的视频采集接口不一样。视频采集模块也具有通用性,不同媒体库的视频采集模块实现的功能是相同的,主要包括如下几个功能:

  1. 检索视频采集设备
  2. 指定视频采集设备进行采集
  3. 根据指定的采集参数(包括分辨率,帧率,图像格式)来初始化设备
  4. 视频帧数据分发

所以如果要实现自己的媒体库,完全可以把webrtc中视频采集模块移植过来。尽管是复用代码,但也需要弄清楚代码的结构,了解它特点和局限性。

我将从来这三个方面:视频采集类,视频数据帧处理类,视频分发框架来分析webrtc视频采集的实现,这篇文章主要介绍前两个方面。

webrtc中视频采集类

webrtc中视频采集模块的代码位于video_capture目录下。建立了两个类体系来定义了视频采集的接口,分别是DeviceInfoVideoCaptureModule。还有一个辅助类VideoCaptureFactory,是个工厂类,用来创建DeviceInfoVideoCaptureModule具体的实现对象。

DeviceInfo

如下为对应的类图

在这里插入图片描述

DeviceInfo可以从接口的名字上看出,这个类提供就是检索视频采集设备及采集能力的接口

  • DeviceInfo为抽象类,定义的都是接口

  • DeviceInofImpl也为抽象类,实现了可以提炼出来的平台通用的接口

  • DeviceInfoDsDeviceInfoLinux分别对应的是windows和linux的视频采集实现了

  • 采集设备有唯一标示,接口中对应为deviceUniqueIdUTF8

GetBestMatchedCapability

它有个GetBestMatchedCapability接口要着重的讲一下,它实现了如何决策视频采集参数,所谓的视频采集参数包括:分辨率,帧率,图像格式 ,分别决定了视频的清晰度,流畅度和保真度。

GetBestMatchedCapability中有个requested参数就表示上层业务指定的参数,但是指定的参数与设备的实际支持的参数可能匹配不了,最坏的情况是所有的都匹配不上。所以往往是找一个最接近而不是完全匹配的,比如:设置的参数是 640480 30fps,但是设备支持的有1080P(19201080 30fps),720P(1080*720 30fps),那么720P是比1080P更加适合。

GetBestMatchedCapability它选取最佳匹配的策略是:先保证分辨率,再保证帧率,再保图像格式。也就是说分辨率的优先级是最高的,如果采集设备有640*480的图像,但是只有15fps,则还是会选择该采集参数。

采集参数的选取至关重要,如果选择的帧率比较低,则图像看的就不流畅。图像格式YUV422比YUV420就更加保真。所以这种视频参数的匹配策略是根据场景需要来确定,可以优先匹配帧率,或优先匹配图像格式。

VideoCaptureModule

VideoCaptureModule类提供了视频采集的接口,如下类图
在这里插入图片描述

  • VideoCaptureModule为抽象类,定义的都是接口
  • VideoCaptureImpl也为抽象类,实现了可以提炼出来的平台通用的接口
  • VideoCaptureDs是windows平台的实现,通过directshow实现。VideoCaptureModuleV4L2是linux平台的实现,通过V4L2实现。抛出视频帧的方式都是通过回调。

VideoCaptureModule中接口接口都定义的比较清晰易懂,可以直接看代码看看它们的具体实现。

VideoCaptureFactory

在这里插入图片描述

VideoCaptureFactory比较简单,提供 创建VideoCaptureModuleDeviceInfo接口

示例

通过 VideoCaptureModuleDeviceInfoVideoCaptureFactory提供的接口可以很方便的实现视频采集,如下是个简单示例代码

webrtc::VideoCaptureModule::DeviceInfo* pDevideInfo = webrtc::VideoCaptureFactory::CreateDeviceInfo(); // 1
uint32_t number = pDevideInfo->NumberOfDevices();// 2
std::cout <<"video caputre number "<<number << std::endl;

char deviceName[128] = { 0 };
char deviceUniqueId[128] = { 0 };
pDevideInfo->GetDeviceName(0, deviceName, 128, deviceUniqueId, 128); // 3
std::cout << "device name: " << deviceName << ",device unique id: " << deviceUniqueId << std::endl;

webrtc::VideoCaptureCapability cap;
int32_t capNum = pDevideInfo->NumberOfCapabilities(deviceUniqueId); // 4
std::cout << "cap num " << capNum<<std::endl;

webrtc::VideoCaptureCapability requestCap; // 5
requestCap.width = 352;
requestCap.height = 288;
requestCap.maxFPS = 30;
requestCap.videoType = mediaengine::VideoType::kI420;

webrtc::VideoCaptureCapability bestCap; // 6
int32_t num = pDevideInfo->GetBestMatchedCapability(deviceUniqueId, requestCap, bestCap); // 7

std::cout << "best match num " << num<<std::endl;

//rtc::scoped_refptr<VideoCaptureModule> pVCM = webrtc::VideoCaptureFactory::Create(deviceUniqueId); // 8
//pVCM->StartCapture(bestCap);// 9
  • 在代码1处创建了一个DeviceInfo对象,可以检索系统中所有可用的采集设备
  • 在代码3处通过GetDeviceName()方法,获取第一个采集设备的deviceUniqueId,这是这个采集设备的唯一标识
  • 在代码4处通过NumberOfCapabilities()方法,获取采集设备支持的能力(视频采集参数)
  • 在代码5处定义了一个VideoCaptureCapability对象,表示业务需要的能力(视频采集参数)
  • 在代码7处调用GetBestMatchedCapability()方法,获取最匹配的能力(视频采集参数)
  • 在代码8和9,创建了一个VideoCaptureModule对象,然后调用StartCapture()方法,启动采集。这里的代码注释了,因为在启动采集之前,需要通过RegisterCaptureDataCallback方法往VideoCaptureModule对象中注册一个回调,用于处理视频帧数据

视频帧处理

采集到视频帧后,会将视频帧数据通过回调抛到业务层。业务层根据需要会对图像进行处理,包括:格式转换,分辨率缩放,图像旋转。

  • 格式转换

在前面提到过视频采集参数,其中就包括图像格式。在决策最适合的视频采集参数时,采集的图像格式可能并不是需要的格式。典型地,编码器需要的图像格式是YUV420,如果采集的视频格式并不是YUV420,就涉及到图像格式的转换

  • 分辨率缩放

在视频帧分发时,会涉及对分辨率的缩放。所谓的视频分发就是,采集的视频帧会有不同的用途,比如会用于编码,会用于回显等。

  • 图像旋转

在移动端中,涉及到旋转图像,比如从竖屏变为横屏

在webrtc中这些图像处理都封装在图像格式代表的类中,它们都属于VideoFrameBuffer体系。表示一个视频帧数据(图像),可以代表对应格式的视频帧。整个类体系如下
在这里插入图片描述

VideoFrameBuffer是个抽象类,在最末端的I420Buffer为具体实现类,还有NV12BufferI010Buffer也是VideoFrameBuffer的实例类。通过GetI420,Get010,GetNV12等接口可以获取代表具体图像格式的类。这里主要介绍I420Buffer类,因为它的使用范围最广。

I420Buffer

上面的类图只是罗列出了图像处理的几个接口,它还有几个用于构造的I420Buffer的静态方法,如下:

static rtc::scoped_refptr<I420Buffer> Create(int width, int height);
static rtc::scoped_refptr<I420Buffer> Create(int width,
                                               int height,
                                               int stride_y,
                                               int stride_u,
                                               int stride_v);

stride_y,stride_u,stride_v分别代表Y分量,U分量,V分量的步长。这两个方法的意义是相同的,因为通过width,height可以算出Y,U,V分量的步长。

下面几个方法是用于图像处理的方法

  • CropAndScaleFrom
void CropAndScaleFrom(const I420BufferInterface& src,
                        int offset_x,
                        int offset_y,
                        int crop_width,
                        int crop_height);

从src上裁剪一部分区域,缩放至this的大小(width,height) ,I420BufferInterface代表的就是一个I420帧

  • ScaleFrom
void ScaleFrom(const I420BufferInterface& src);

将src缩放至this的大小(width,height)

  • PasteFrom
void PasteFrom(const I420BufferInterface& picture,
                 int offset_col,
                 int offset_row);

以this为画布,将picture贴在offset_col,offset_row处

  • Rotate
static rtc::scoped_refptr<I420Buffer> Rotate(const I420BufferInterface& src,
                                               VideoRotation rotation);

将 src 旋转,产生一个新的I420Buffer

在媒体库中,最主要处理YUV420的图像,所以媒体库中复用I420Buffer就足够,没必要引入VideoFrameBuffer整个体系。

猜你喜欢

转载自blog.csdn.net/mo4776/article/details/122821078