关于编码:基于-WebRTC-实现自定义编码分辨率发送

49次阅读

共计 9064 个字符,预计需要花费 23 分钟才能阅读完成。

2020 年如果问什么技术畛域最火?毫无疑问:音视频。2020 年近程办公和在线教育的强势倒退,都离不开音视频的身影,视频会议、在线教学、娱乐直播等都是音视频的典型利用场景。

更加丰盛的应用场景更须要咱们思考 如何提供更多的可配置能力项,比方分辨率、帧率、码率等 ,以实现更好的用户体验。本文将次要从“ 分辨率”开展具体分享。

如何实现自定义编码分辨率

咱们先来看看“分辨率”的定义。分辨率:是度量图像内像素数据量多少的一个参数,是 掂量一帧图像或视频品质的要害指标 。分辨率越高,图像体积(字节数)越大,画质越好。对于一个 YUV i420 格局、分辨率 1080p 的视频流来说,一帧图像的体积为 1920x1080x1.5×8/1024/1024≈23.73Mbit,帧率 30,则 1s 的大小是 30×23.73≈711.9Mbit。可见数据量之大,对码率要求之高,所以在理论传输过程中就须要对视频进行压缩编码。因而,视频采集设施采集出的原始数据分辨率咱们称 采集分辨率,理论送进编码器的数据分辨率咱们就称之为 编码分辨率

视频画面是否清晰、比例是否适合,这些都会间接影响用户体验。摄像头采集分辨率的抉择是无限的,有时咱们想要的分辨率并不能间接通过摄像头采集到。那么,依据场景配置适合编码分辨率的能力就至关重要了。如何将采集到的视频转换成咱们想要的编码分辨率去发送?这就是咱们明天的次要分享的内容。

WebRTC 是 Google 开源的,功能强大的实时音视频我的项目,市面上大多开发者都是基于 WebRTC 构建实时音视频通信的解决方案。在 WebRTC 中各个模块都有很好的形象解耦解决, 对咱们进行二次开发十分敌对。在咱们构建实时音视频通信解决方案时,须要去理解和学习 WebRTC 的设计思维及代码模块,并具备二次开发和扩大的能力。本文咱们基于 WebRTC Release 72 版本,聊聊如何实现自定义编码分辨率。

首先,咱们思考上面几个问题:

  • 视频数据从采集到编码发送,其 Pipeline 是怎么的?
  • 怎么依据设置的编码分辨率抉择适合的采集分辨率?
  • 怎么能失去想要的编码分辨率?

本文内容也将从以上三点开展具体分享。

视频数据的 Pipeline

首先,咱们来理解一下视频数据的 Pipeline。视频数据由 VideoCapturer 产生,VideoCapturer 采集数据后通过 VideoAdapter 解决,而后经由 VideoSource 的 VideoBroadcaster 分发给注册的 VideoSink,VideoSink 即编码器 Encoder Sink 和本地预览 Preview Sink。

对视频分辨率来说,流程是:将想要的分辨率设置给 VideoCapturer,VideoCapturer 抉择适合的分辨率去采集,原始的采集分辨率数据再通过 VideoAdapter 计算,不合乎预期后再进行缩放裁剪失去编码分辨率的视频数据,将数据再送进编码器编码后发送。

这里就有两个关键性问题:

  • VideoCapturer 如何抉择适合的采集分辨率?
  • VideoAdapter 如何将采集分辨率转换成编码分辨率?

如何抉择适合的采集分辨率

采集分辨率的抉择

WebRTC 中对视频采集形象出一个 Base 类:videocapturer.cc,咱们把形象称为 VideoCapturer,在 VideoCapturer 中设置参数属性,比方视频分辨率、帧率、反对的像素格局等,VideoCapturer 将依据设置的参数,计算出最佳的采集格局,再用这个采集格局去调用各个平台的 VDM(Video Device Module,视频硬件设施模块)。具体的设置如下:

代码摘自 WebRTC 中 src/media/base/videocapturer.h

VideoCapturer.h
bool GetBestCaptureFormat(const VideoFormat& desired, VideoFormat* best_format);// 外部遍历设施反对的所有采集格局调用 GetFormatDistance()计算出每个格局的 distance,选出 distance 最小的那个格局
int64_t GetFormatDistance(const VideoFormat& desired, const VideoFormat& supported);// 依据算法计算出设施反对的格局与咱们想要的采集格局的差距,distance 为 0 即刚好满足咱们的设置
void SetSupportedFormats(const std::vector<VideoFormat>& formats);// 设置采集设施反对的格局 fps,resolution,NV12,I420,MJPEG 等       

依据设置的参数,有时 GetBestCaptureFormat() 并不能失去比拟合乎咱们设置的采集格局,因为不同的设施采集能力不同,iOS、Android、PC、Mac 原生的摄像采集和外置 USB 摄像采集对分辨率的反对是不同的,尤其外置 USB 摄像采集能力参差不齐。因而,咱们须要对 GetFormatDistance() 稍作调整以满足咱们的需要,上面咱们就来聊聊具体应该如何进行代码调整以满足需要。

抉择策略源码剖析

咱们先剖析一下 GetFormatDistance() 的源码,摘取局部代码:

代码摘自 WebRTC 中 src/media/base/videocapturer.cc

// Get the distance between the supported and desired formats.
int64_t VideoCapturer::GetFormatDistance(const VideoFormat& desired,
                                         const VideoFormat& supported) {
  //.... 省略局部代码
  // Check resolution and fps.
  int desired_width = desired.width;// 编码分辨率宽
  int desired_height = desired.height;// 编码分辨率高
  int64_t delta_w = supported.width - desired_width;// 宽的差
  
  float supported_fps = VideoFormat::IntervalToFpsFloat(supported.interval);// 采集设施反对的帧率
  float delta_fps = supported_fps - VideoFormat::IntervalToFpsFloat(desired.interval);// 帧率差
  int64_t aspect_h = desired_width
                         ? supported.width * desired_height / desired_width
                         : desired_height;// 计算出设置的宽高比的高,采集设施的分辨率反对个别宽 > 高
  int64_t delta_h = supported.height - aspect_h;// 高的差
  int64_t delta_fourcc;// 设置的反对像素格局优先程序,比方优先设置了 NV12,同样分辨率和帧率的状况优先应用 NV12 格局采集
  
  //.... 省略局部降级策略代码,次要针对设施反对的分辨率和帧率不满足设置后的降级策略
  
  int64_t distance = 0;
  distance |=
      (delta_w << 28) | (delta_h << 16) | (delta_fps << 8) | delta_fourcc;

  return distance;
}

咱们次要关注 Distance 这个参数。Distance 是 WebRTC 中的概念,它是设置的采集格局与设施反对的采集格局依照肯定算法策略计算出的差值,差值越小代表设施反对的采集格局与设置想要的格局越靠近,为 0 即刚好匹配。

Distance 由四局部组成 delta_w,delta_h,delta_fps,delta_fourcc,其中 delta_w(分辨率宽)权重最重,delta_h(分辨率高)其次,delta_fps(帧率)再次,delta_fourcc(像素格局)最初。这样导致的问题是宽的比重太高, 高的比重太低,无奈匹配到比拟准确反对的分辨率。

Example:

以 iPhone xs Max 800×800 fps:10 为例,咱们摘取局部采集格局的 distance, 原生的 GetFormatDistance() 的算法是不满足需要的,想要的是 800×800,能够从下图看出后果 Best 是 960×540,不合乎预期:

Supported NV12 192x144x10 distance 489635708928
Supported NV12 352x288x10 distance 360789835776
Supported NV12 480x360x10 distance 257721630720
Supported NV12 640x480x10 distance 128880476160
Supported NV12 960x540x10 distance 43032248320
Supported NV12 1024x768x10 distance 60179873792
Supported NV12 1280x720x10 distance 128959119360
Supported NV12 1440x1080x10 distance 171869470720
Supported NV12 1920x1080x10 distance 300812861440
Supported NV12 1920x1440x10 distance 300742082560
Supported NV12 3088x2316x10 distance 614332104704
Best NV12 960x540x10 distance 43032248320

抉择策略调整

为了获取咱们想要的分辨率,依照咱们剖析,须要明确调整 GetFormatDisctance() 的算法,将分辨率的权重调整为最高,帧率其次,在没有指定像素格局的状况下,像素格局最初,那么批改状况如下:

int64_t VideoCapturer::GetFormatDistance(const VideoFormat& desired,
const VideoFormat& supported) {
 //.... 省略局部代码
  // Check resolution and fps.
int desired_width = desired.width; // 编码分辨率宽
int desired_height = desired.height; // 编码分辨率高
  int64_t delta_w = supported.width - desired_width;
  int64_t delta_h = supported.height - desired_height;
  int64_t delta_fps = supported.framerate() - desired.framerate();
  distance = std::abs(delta_w) + std::abs(delta_h);
  //.... 省略降级策略, 比方设置了 1080p,然而摄像采集设施最高反对 720p,须要降级
  distance = (distance << 16 | std::abs(delta_fps) << 8 | delta_fourcc);
return distance;
}

批改后:Distance 由三局部组成分辨率 (delta_w+delta_h),帧率 delta_fps,像素 delta_fourcc,其中 (delta_w+delta_h) 比重最高,delta_fps 其次,delta_fourcc 最初。

Example:

还是以 iPhone xs Max 800×800 fps:10 为例,咱们摘取局部采集格局的 Distance, GetFormatDistance() 批改后, 咱们想要的是 800×800, 抉择的 Best 是 1440×1080, 咱们能够通过缩放裁剪失去 800×800, 合乎预期(对分辨率要求不是特地准确的状况下,能够调整降级策略,抉择 1024×768):

Supported NV12 192x144x10 distance 828375040
Supported NV12 352x288x10 distance 629145600
Supported NV12 480x360x10 distance 498073600
Supported NV12 640x480x10 distance 314572800
Supported NV12 960x540x10 distance 275251200
Supported NV12 1024x768x10 distance 167772160
Supported NV12 1280x720x10 distance 367001600
Supported NV12 1440x1080x10 distance 60293120
Supported NV12 1920x1080x10 distance 91750400
Supported NV12 1920x1440x10 distance 115343360
Supported NV12 3088x2316x10 distance 249298944
Best NV12 1440x1080x10 distance 60293120

如何实现采集分辨率到编码分辨率

视频数据采集实现后,会通过 VideoAdapter (WebRTC 中的形象) 解决再散发到对应的 Sink (WebRTC 中的形象)。咱们在 VideoAdapter 中稍作调整以计算出缩放裁剪所需的参数,再把视频数据用 LibYUV 缩放再裁剪到编码分辨率(为了尽可能保留多的画面图像信息,先用缩放解决,宽高比不统一时再裁剪多余的像素信息)。这里咱们重点剖析两个问题:

  • 还是选用下面的例子,咱们想要的分辨率为 800×800,然而咱们失去的最佳采集分辨率为 1440×1080,那么,如何从 1440×1080 采集分辨率失去设置的编码分辨率 800×800 呢?
  • 在视频数据从 VideoCapture 流到 VideoSink 的过程中会通过 VideoAdapter 的解决,VideoAdapter 具体做了哪些事呢?

上面咱们就这两个问题开展具体的剖析,咱们先理解一下 VideoAdapter 是什么。

VideoAdapter 介绍

WebRTC 中对 VideoAdapter 是这样形容的:

VideoAdapter adapts an input video frame to an output frame based on the specified input and output formats. The adaptation includes dropping frames to reduce frame rate and scaling frames.VideoAdapter is
thread safe.

咱们能够了解为:VideoAdapter 是数据输入输出管制的模块,能够对帧率、分辨率做对应的帧率控制和分辨率降级。在 VQC(Video Quality Control)视频品质管制模块里,通过对 VideoAdapter 的配置,能够做到在低带宽、高 CPU 状况下对帧率进行动静降帧,对分辨率进行动静缩放,以保障视频的流畅性,从而进步用户体验。

摘自 src/media/base/videoadapter.h

VideoAdapter.h
bool AdaptFrameResolution(int in_width,
int in_height,
                            int64_t in_timestamp_ns,
int* cropped_width,
int* cropped_height,
int* out_width,
int* out_height);
void OnOutputFormatRequest(
const absl::optional<std::pair<int, int>>& target_aspect_ratio,
const absl::optional<int>& max_pixel_count,
const absl::optional<int>& max_fps);
void OnOutputFormatRequest(const absl::optional<VideoFormat>& format);

VideoAdapter 源码剖析

VideoAdapter 中依据设置的 desried_format,调用 AdaptFrameResolution(),能够计算出采集分辨率到编码分辨率应该缩放和裁剪的 cropped_width, cropped_height, out_width, out_height 参数, WebRTC 原生的 adaptFrameResolution 是依据计算像素面积计算缩放参数,而不能失去准确的宽 & 高:

摘自 src/media/base/videoadapter.cc

bool VideoAdapter::AdaptFrameResolution(int in_width,
int in_height,
                                        int64_t in_timestamp_ns,
int* cropped_width,
int* cropped_height,
int* out_width,
int* out_height) {
//..... 省略局部代码
// Calculate how the input should be cropped.
if (!target_aspect_ratio || target_aspect_ratio->first <= 0 ||
        target_aspect_ratio->second <= 0) {
      *cropped_width = in_width;
      *cropped_height = in_height;
    } else {
const float requested_aspect =
          target_aspect_ratio->first /
static_cast<float>(target_aspect_ratio->second);
      *cropped_width =
          std::min(in_width, static_cast<int>(in_height * requested_aspect));
      *cropped_height =
          std::min(in_height, static_cast<int>(in_width / requested_aspect));
    }
const Fraction scale;//vqc 缩放系数 .... 省略代码
    // Calculate final output size.
    *out_width = *cropped_width / scale.denominator * scale.numerator;
    *out_height = *cropped_height / scale.denominator * scale.numerator;
 }

Example:

以 iPhone xs Max 800×800 fps:10 为例,设置编码分辨率为 800×800,采集分辨率是 1440×1080,依据原生的算法,计算失去的新的分辨率为 720×720, 不合乎预期。

VideoAdapter 调整

VideoAdapter 是 VQC(视频品质管制模块)中对视频品质做调整的重要局部,VQC 之所以能够实现帧率控制、分辨率缩放等操作,次要依赖于 VideoAdapter,因而批改须要思考对 VQC 的影响。

为了能准确取得想要的分辨率,且不影响 VQC 模块对分辨率的管制,咱们对 AdaptFrameResolution() 做以下调整:

bool VideoAdapter::AdaptFrameResolution(int in_width,
int in_height,
                                        int64_t in_timestamp_ns,
int* cropped_width,
int* cropped_height,
int* out_width,
int* out_height) {
  //.... 省略局部代码
bool in_more =
        (static_cast<float>(in_width) / static_cast<float>(in_height)) >=
        (static_cast<float>(desired_width_) /
static_cast<float>(desired_height_));
if (in_more) {
        *cropped_height = in_height;
        *cropped_width = *cropped_height * desired_width_ / desired_height_;
    } else {
      *cropped_width = in_width;
      *cropped_height = *cropped_width * desired_height_ / desired_width_;
    }
    *out_width = desired_width_;
    *out_height = desired_height_;
    //.... 省略局部代码
return true;
}

Example:

同样以 iPhone xs Max 800×800 fps:10 为例,设置编码分辨率为 800×800,采集分辨率是 1440×1080,依据调整后的算法,计算失去的编码分辨率为 800×800, 合乎预期。

总结

本文次要介绍了基于 WebRTC 如何实现编码分辨率的配置。当咱们要对视频编码分辨率进行批改时,就须要去理解视频数据的采集、传递、解决、编码等整个流程,这里也再对明天分享几个关键步骤进行演绎,当咱们要实现自定义编码分辨率发送时:

  • 首先,设置想要的编码分辨率;
  • 批改 VideoCapturer.cc,依据编码分辨率抉择适合的采集分辨率;
  • 批改 VideoAdapter.cc,计算出采集分辨率缩放和裁剪到编码分辨率所需的参数;
  • 依据缩放和裁剪参数应用 libyuv 将原始数据缩放裁剪到编码分辨率;
  • 再将新数据送进编码器编码并发送;
  • 最初,Done。

同理咱们也能够根据这种思路去做一些其余的调整。以上就是本文的全副介绍,咱们也会继续分享更多音视频相干的技术实现,也欢送留言与咱们交换相干的技术。

5G 时代曾经降临,音视频的应用领域会越来越宽泛,所有大有可为。

作者介绍

何敬敬,网易云信客户端音视频工程师,负责云信音视频跨平台 SDK 的研发工作。之前从事在线教育畛域的音视频工作,对构建实时音视频解决方案和应用场景有肯定的了解,喜爱钻研和解决简单技术问题。

更多技术干货,欢送关注【网易智企技术 +】微信公众号

正文完
 0