WebRTC Android native SDK 支持 Simulcast 的代码改造

注:本文内容基于WebRTC M76分支,部分代码细节可能和后续WebRTC代码有差异。但笔者查看了4324(M88),基本上差异不大,同样适用,但再往后的版本就无法保证了。

首先说一句,simulcast的支持,不仅仅是客户端修改就可以了,服务器端也需要修改。Licode、mediasoup、janus这些比较流行的WebRTC Server方案都是支持simulcast的。这里只描述了客户端的修改,不涉及服务器端。

如果你对simulcast所涉及的两种sdp格式不太熟悉,可以先看看这篇文章:Simulcast and Janus: what’s new? (and where’s my SSRC?) ,这里面非常详细地给出了simulcast的两种sdp显示形态。

下面是在Chrome中使用js推送2个分辨率的sdp示例:

本地SDP:
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 124 119 123 118 114 115 116
此处省略大幅无关内容
a=rid:r0 send
a=rid:r1 send
a=simulcast:send r0;r1

远端SDP:
m=video 7 RTP/SAVPF 125 107
此处省略大幅无关内容
a=rid:r0 recv
a=rid:r1 recv
a=simulcast:recv r0;r1

在这里插入图片描述
在这里插入图片描述
在Web里通过js实现simulcast是非常简单方便的,Chrome自身已经支持了。但是对于Android native SDK来说,就不是那么容易了。甚至我发现Android native SDK中是没有simulcast encoder adapter的调用的。如上图中看到的SimulcastEncoderAdapter,对应C++代码是:media\engine\simulcast_encoder_adapter.cc,而在webrtc的sdk目录下搜索 SimulcastEncoderAdapter 是找不到任何调用的。所以,让Android native SDK支持simulcast,主要是把WebRTC代码中的 SimulcastEncoderAdapter 这个类利用起来。

simulcast sdp的产生

根据上面提到的文章中描述的那样,在Android中也可以使用类似的方法来产生。

如果你使用的是unified plan格式的sdp,产生simulcast的关键代码如下:

List<RtpParameters.Encoding> encodings = new ArrayList<RtpParameters.Encoding>();
encodings.add(new RtpParameters.Encoding("main_stream", true, 1.0));
// 2.0 表示第2个分辨率取主分辨率的一半,如果是4.0那就是1/4
encodings.add(new RtpParameters.Encoding("second_stream", true, 2.0));

RtpTransceiver.RtpTransceiverInit init = new RtpTransceiver.RtpTransceiverInit(
    RtpTransceiver.RtpTransceiverDirection.SEND_RECV, Collections.emptyList(), encodings);
// pc是PeerConnection    
RtpTransceiver transceiver = pc.addTransceiver(MediaStreamTrack.MediaType.MEDIA_TYPE_VIDEO, init);

在webrtc M76中,RtpParameters.Encoding是私有的,我们需要把它变成public,代码位于:\sdk\android\api\org\webrtc\RtpParameters.java。不过如果你使用的是比较新的webrtc版本,这个不需要做。因为在 2019.12.3这天,名为Amit Hilbuch的作者已经把它改成了public了(SHA-1: e725fdbcc11abb82ad64b7e90d6aa5d576e18e32)

如果你使用的是plan-b格式的sdp,关键代码如下:

MediaConstraints sdpConstraints = new MediaConstraints();
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNumSimulcastLayers", Integer.toString(2)));
pc.createOffer(SdpObserver, sdpConstraints);

不过比较推荐使用unified plan,这个与web端保持了一致的方式,方便对比调试,也是更靠近webrtc新标准的方式。

引入SimulcastEncoderAdapter

实现simulcast比较关键的是把 webrtc的SimulcastEncoderAdapter这个类拿过来用一下。让我们先来简单回顾一下Android上视频编码器的创建时序。
假设我们调用的是 WebRTC Android native SDK 的默认编码器工厂 DefaultVideoEncoderFactory 来创建编码器,根据 sdk\android\api\org\webrtc\DefaultVideoEncoderFactory.java 的实现,可能通过 HardwareVideoEncoderFactory 创建硬编码器,也可能通过 SoftwareVideoEncoderFactory 创建软编码器,也可能是软硬结合的一个wrapper:VideoEncoderFallback。然后在构造PeerConnectionFactory.builder对象的时候,将编码器工厂传过去(详情见PeerConnectionFactory.java的实现)即可。之后会通过JNI层层调用到native层,顺序是这样:

  • JNI_PeerConnectionFactory_CreatePeerConnectionFactory (peer_connection_factory.cc)
  • CreateVideoEncoderFactory (video.cc)
  • VideoEncoderFactoryWrapper ctor (video_encoder_factory_wrapper.cc)
    其中,VideoEncoderFactoryWrapper::CreateVideoEncoder()的实现是这样的:
std::unique_ptr<VideoEncoder> VideoEncoderFactoryWrapper::CreateVideoEncoder(
    const SdpVideoFormat& format) {
  JNIEnv* jni = AttachCurrentThreadIfNeeded();
  ScopedJavaLocalRef<jobject> j_codec_info =
      SdpVideoFormatToVideoCodecInfo(jni, format);
  ScopedJavaLocalRef<jobject> encoder = Java_VideoEncoderFactory_createEncoder(
      jni, encoder_factory_, j_codec_info);
  if (!encoder.obj())
    return nullptr;
  return JavaToNativeVideoEncoder(jni, encoder);
}

它会调用相应的VideoEncoderFatory的createEncoder来完成编码器的创建。例如,调用 HardwareVideoEncoderFactory.createEncoder() 来完成硬编码器的创建。调用SoftwareVideoEncoderFactory.createEncoder()来完成libvp8/vp9(默认只支持VPX,你可以手动添加H264软编码器)编码器的创建。

因此,我们只要在这里进行“偷梁换柱”,把Java_VideoEncoderFactory_createEncoder换掉,直接创建一个 SimulcastEncoderAdapter(它也继承于VideoEncoder)对象返回即可。而 SimulcastEncoderAdapter 就是我们刚才在Chrome浏览器看到的那个Simulcast的实现,里面的大部分代码基本上都是拿来即用的。

下面是经过改造以后,从VideoStreamEncoder (video\video_stream_encoder.cc) 中发起的,创建、设置、初始化编码器的调用时序图:
在这里插入图片描述
从上图中,可以基本上看到SimulcastEncoderAdapter作为一个编码器“中介”所起到的作用。

以上就是针对Android native SDK中增加Simulcast推流2个或者更多分辨率的实现思路。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值