searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

webrtc windows端视频硬件加速方案编码篇

2023-08-25 01:49:46
252
0

1. 介绍

webrtc全称为web Real-time communication,使用此技术可以为应用添加基于开发标准的实时通信能力。它支持在设备之间发送音视频和通用数据,为用户提供强大的音视频通信解决方案。

2. 背景

webrtc windows端在进行桌面或窗口共享时,由于采集出的分辨率较大,如果仍然使用软件编码,会对机器带来较大的压力,同时引入了更多的编码延时。为此我们对webrtc编码部分进行了补充,添加了硬件加速编码,同时适配了部分机器不支持硬件编码方案或硬编失败时,自动回退到软件编码,提高产品的稳定性。

本章主要简单分享编码部分的一些优化及问题。

3. 主要工作

3.1 选择硬件编码方案

支持D3D11硬件加速的编码器没有跨平台的,不同GPU下的编码方式不同。所以如果使用调用各个GPU编码器API进行编码的话,工作量很大,由于资源有限,所以采用了ffmpeg的方案进行硬件加速。

在选择使用ffmpeg作为硬件编码方案后,但同时引入了新的问题:

  1. 由于webrtc自带ffmpeg源码,如果再使用新的ffmpeg库会带来包的冗余,也不利于维护。因此需要修改webrtc支持ffmpeg的硬件编码。
  2. NVIDIA的编码器硬件加速默认使用的D3D11纹理,但Intel的QSV使用的是qsv纹理,需要进行纹理的映射转换。

3.2 添加硬件编码器

创建硬件编码器工厂,添加硬件编码器,将编码器工厂传递给webrtc的CreatePeerConnectionFactory,此过程不再细说。重点说一下ffmpeg初始化硬件设备上下文部分。

参考ffmpeg的vaapi_encode.c中的set_hwframe_ctx方法,在创建设备上下文时,直接调用av_hwdevice_ctx_create方法。但我们的帧数据是D3D11纹理,和ffmpeg中的创建编码器纹理的上下文不同,如果使用av_hwdevice_ctx_create直接调用,那么就需要使用共享纹理去处理,为了不引入纹理间更多了操作,采用了自定义创建设备上下文的方式。

可以详细看一下av_hwdevice_ctx_create的实现,三个重要的方法av_hwdevice_ctx_alloc、device_ctx->internal->hw_type->device_create和av_hwdevice_ctx_init,下面我们自定义实现设备上下文的创建:

// Create D3D11 Hardware Device Context.
hwDeviceCtx.reset(av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA));
if (hwDeviceCtx == nullptr) {
  RTC_LOG(LS_ERROR) << "Failed to alloc hardware device context.";
  ret = WEBRTC_VIDEO_CODEC_MEMORY;
  break;
}

auto deviceCtxData = (AVHWDeviceContext*)hwDeviceCtx->data;
if (deviceCtxData == nullptr) {
  RTC_LOG(LS_ERROR) << "Failed to get hardware device context.";
  ret = WEBRTC_VIDEO_CODEC_ERROR;
  break;
}

auto d3d11DevCtx = (AVD3D11VADeviceContext*)deviceCtxData->hwctx;
if (d3d11DevCtx == nullptr) {
  RTC_LOG(LS_ERROR) << "Failed to get d3d device context.";
  ret = WEBRTC_VIDEO_CODEC_ERROR;
  break;
}

memset(d3d11DevCtx, 0, sizeof(AVD3D11VADeviceContext));

// bug: avcodec_free_context will release the d3device.
d3device->AddRef();
d3d11DevCtx->device = d3device.Get();

auto doNothing = [](void*) {};
d3d11DevCtx->lock_ctx = (void*)1;
d3d11DevCtx->lock = doNothing;
d3d11DevCtx->unlock = doNothing;

auto err = av_hwdevice_ctx_init(hwDeviceCtx.get());
if (err < 0) {
  RTC_LOG(LS_ERROR) << "Failed to init hardware device." << err;
  ret = WEBRTC_VIDEO_CODEC_ERROR;
  break;
}

需要注意两个地方:

  1. 传入的D3D设备,需要增加引用计数,ffmpeg在退出时会自动将引用计数减一,如果没有增加引用计数,会导致D3D设备被释放。
  2. QSV的设备类型是AV_HWDEVICE_TYPE_QSV,NVIDIA的设备类型为AV_HWDEVICE_TYPE_D3D11VA,AMD的设备类型也是AV_HWDEVICE_TYPE_D3D11VA,所以针对QSV需要进行设备派生映射
// 在创建D3D11VA设备上下文后(即上面代码后),添加如下代码
if (avHwDeviceType == AV_HWDEVICE_TYPE_QSV) {
  // create derived hardware context.
  AVBufferRef* avHwDevCtxDerived = nullptr;
  auto err = av_hwdevice_ctx_create_derived(
      &avHwDevCtxDerived, avHwDeviceType, hwDeviceCtx.get(), 0);
  if (err < 0) {
    RTC_LOG(LS_ERROR) << "Failed to create derived hardware context.";
    ret = WEBRTC_VIDEO_CODEC_MEMORY;
    break;
  }

  hwDeviceCtx.reset(avHwDevCtxDerived);
}

3.3 硬件编码

通过3.2创建硬件设备上下文后,就可以出创建该硬件上下文的视频帧,然后需要将采集到的源纹理数据传递给视频帧。

设备类型是AV_HWDEVICE_TYPE_D3D11VA的,帧类型为AV_PIX_FMT_D3D11;而AV_HWDEVICE_TYPE_QSV设备的帧类型为AV_PIX_FMT_QSV。

因此NVIDIA和AMD设备上的帧数据传递,只需要调用D3D11纹理拷贝即可。

而Intel设备的帧数据需要进行纹理映射,将视频帧的QSV纹理数据映射为D3D11纹理,然后将源数据数据拷贝即可。

  // If this is a frame from a derived context,
  // we'll need to map it to D3D11
  ComPtr<ID3D11Texture2D> dstTexture = nullptr;
  if (avHWFrame->format != AV_PIX_FMT_D3D11) {
    ScopedAVFrame d3d11Frame(av_frame_alloc());

    d3d11Frame->format = AV_PIX_FMT_D3D11;

    int err = av_hwframe_map(d3d11Frame.get(), avHWFrame.get(),
                             AV_HWFRAME_MAP_WRITE | AV_HWFRAME_MAP_OVERWRITE);
    if (err < 0) {
      RTC_LOG(LS_ERROR) << "Failed to map D3D11 frame.";
      return WEBRTC_VIDEO_CODEC_ERROR;
    }

    // Get the texture from the mapped frame
    dstTexture = (ID3D11Texture2D*)d3d11Frame->data[0];
  } else {
    // Otherwise, we can just use the texture inside the original frame
    dstTexture = (ID3D11Texture2D*)avHWFrame->data[0];
  }

数据准备完成,ffmpeg编码部分就大同小异了。

4 总结

本章主要简单分享了windows下不同GPU下硬解加速的实现,仍然还有许多问题需要解决,比如采集到的纹理数据是BGRA的,但是编码器只能使用nv12格式,因此需要将bgra纹理转化为nv12。还有为了编码器支持vbr模式,不同的GPU下编码器的配置方案是不同的,这个需要关注。再有就是webrtc需要在编码中根据网络质量动态调节编码器目标码率,而有的ffmpeg版本过低的话,编码器是不支持动态调节的;等等这些一些问题都需要我们就去修改测试适配。

0条评论
0 / 1000
李****达
4文章数
1粉丝数
李****达
4 文章 | 1 粉丝
原创

webrtc windows端视频硬件加速方案编码篇

2023-08-25 01:49:46
252
0

1. 介绍

webrtc全称为web Real-time communication,使用此技术可以为应用添加基于开发标准的实时通信能力。它支持在设备之间发送音视频和通用数据,为用户提供强大的音视频通信解决方案。

2. 背景

webrtc windows端在进行桌面或窗口共享时,由于采集出的分辨率较大,如果仍然使用软件编码,会对机器带来较大的压力,同时引入了更多的编码延时。为此我们对webrtc编码部分进行了补充,添加了硬件加速编码,同时适配了部分机器不支持硬件编码方案或硬编失败时,自动回退到软件编码,提高产品的稳定性。

本章主要简单分享编码部分的一些优化及问题。

3. 主要工作

3.1 选择硬件编码方案

支持D3D11硬件加速的编码器没有跨平台的,不同GPU下的编码方式不同。所以如果使用调用各个GPU编码器API进行编码的话,工作量很大,由于资源有限,所以采用了ffmpeg的方案进行硬件加速。

在选择使用ffmpeg作为硬件编码方案后,但同时引入了新的问题:

  1. 由于webrtc自带ffmpeg源码,如果再使用新的ffmpeg库会带来包的冗余,也不利于维护。因此需要修改webrtc支持ffmpeg的硬件编码。
  2. NVIDIA的编码器硬件加速默认使用的D3D11纹理,但Intel的QSV使用的是qsv纹理,需要进行纹理的映射转换。

3.2 添加硬件编码器

创建硬件编码器工厂,添加硬件编码器,将编码器工厂传递给webrtc的CreatePeerConnectionFactory,此过程不再细说。重点说一下ffmpeg初始化硬件设备上下文部分。

参考ffmpeg的vaapi_encode.c中的set_hwframe_ctx方法,在创建设备上下文时,直接调用av_hwdevice_ctx_create方法。但我们的帧数据是D3D11纹理,和ffmpeg中的创建编码器纹理的上下文不同,如果使用av_hwdevice_ctx_create直接调用,那么就需要使用共享纹理去处理,为了不引入纹理间更多了操作,采用了自定义创建设备上下文的方式。

可以详细看一下av_hwdevice_ctx_create的实现,三个重要的方法av_hwdevice_ctx_alloc、device_ctx->internal->hw_type->device_create和av_hwdevice_ctx_init,下面我们自定义实现设备上下文的创建:

// Create D3D11 Hardware Device Context.
hwDeviceCtx.reset(av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA));
if (hwDeviceCtx == nullptr) {
  RTC_LOG(LS_ERROR) << "Failed to alloc hardware device context.";
  ret = WEBRTC_VIDEO_CODEC_MEMORY;
  break;
}

auto deviceCtxData = (AVHWDeviceContext*)hwDeviceCtx->data;
if (deviceCtxData == nullptr) {
  RTC_LOG(LS_ERROR) << "Failed to get hardware device context.";
  ret = WEBRTC_VIDEO_CODEC_ERROR;
  break;
}

auto d3d11DevCtx = (AVD3D11VADeviceContext*)deviceCtxData->hwctx;
if (d3d11DevCtx == nullptr) {
  RTC_LOG(LS_ERROR) << "Failed to get d3d device context.";
  ret = WEBRTC_VIDEO_CODEC_ERROR;
  break;
}

memset(d3d11DevCtx, 0, sizeof(AVD3D11VADeviceContext));

// bug: avcodec_free_context will release the d3device.
d3device->AddRef();
d3d11DevCtx->device = d3device.Get();

auto doNothing = [](void*) {};
d3d11DevCtx->lock_ctx = (void*)1;
d3d11DevCtx->lock = doNothing;
d3d11DevCtx->unlock = doNothing;

auto err = av_hwdevice_ctx_init(hwDeviceCtx.get());
if (err < 0) {
  RTC_LOG(LS_ERROR) << "Failed to init hardware device." << err;
  ret = WEBRTC_VIDEO_CODEC_ERROR;
  break;
}

需要注意两个地方:

  1. 传入的D3D设备,需要增加引用计数,ffmpeg在退出时会自动将引用计数减一,如果没有增加引用计数,会导致D3D设备被释放。
  2. QSV的设备类型是AV_HWDEVICE_TYPE_QSV,NVIDIA的设备类型为AV_HWDEVICE_TYPE_D3D11VA,AMD的设备类型也是AV_HWDEVICE_TYPE_D3D11VA,所以针对QSV需要进行设备派生映射
// 在创建D3D11VA设备上下文后(即上面代码后),添加如下代码
if (avHwDeviceType == AV_HWDEVICE_TYPE_QSV) {
  // create derived hardware context.
  AVBufferRef* avHwDevCtxDerived = nullptr;
  auto err = av_hwdevice_ctx_create_derived(
      &avHwDevCtxDerived, avHwDeviceType, hwDeviceCtx.get(), 0);
  if (err < 0) {
    RTC_LOG(LS_ERROR) << "Failed to create derived hardware context.";
    ret = WEBRTC_VIDEO_CODEC_MEMORY;
    break;
  }

  hwDeviceCtx.reset(avHwDevCtxDerived);
}

3.3 硬件编码

通过3.2创建硬件设备上下文后,就可以出创建该硬件上下文的视频帧,然后需要将采集到的源纹理数据传递给视频帧。

设备类型是AV_HWDEVICE_TYPE_D3D11VA的,帧类型为AV_PIX_FMT_D3D11;而AV_HWDEVICE_TYPE_QSV设备的帧类型为AV_PIX_FMT_QSV。

因此NVIDIA和AMD设备上的帧数据传递,只需要调用D3D11纹理拷贝即可。

而Intel设备的帧数据需要进行纹理映射,将视频帧的QSV纹理数据映射为D3D11纹理,然后将源数据数据拷贝即可。

  // If this is a frame from a derived context,
  // we'll need to map it to D3D11
  ComPtr<ID3D11Texture2D> dstTexture = nullptr;
  if (avHWFrame->format != AV_PIX_FMT_D3D11) {
    ScopedAVFrame d3d11Frame(av_frame_alloc());

    d3d11Frame->format = AV_PIX_FMT_D3D11;

    int err = av_hwframe_map(d3d11Frame.get(), avHWFrame.get(),
                             AV_HWFRAME_MAP_WRITE | AV_HWFRAME_MAP_OVERWRITE);
    if (err < 0) {
      RTC_LOG(LS_ERROR) << "Failed to map D3D11 frame.";
      return WEBRTC_VIDEO_CODEC_ERROR;
    }

    // Get the texture from the mapped frame
    dstTexture = (ID3D11Texture2D*)d3d11Frame->data[0];
  } else {
    // Otherwise, we can just use the texture inside the original frame
    dstTexture = (ID3D11Texture2D*)avHWFrame->data[0];
  }

数据准备完成,ffmpeg编码部分就大同小异了。

4 总结

本章主要简单分享了windows下不同GPU下硬解加速的实现,仍然还有许多问题需要解决,比如采集到的纹理数据是BGRA的,但是编码器只能使用nv12格式,因此需要将bgra纹理转化为nv12。还有为了编码器支持vbr模式,不同的GPU下编码器的配置方案是不同的,这个需要关注。再有就是webrtc需要在编码中根据网络质量动态调节编码器目标码率,而有的ffmpeg版本过低的话,编码器是不支持动态调节的;等等这些一些问题都需要我们就去修改测试适配。

文章来自个人专栏
webrtc
4 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0