WEBRTC基本概念
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WEBRTC基本概念相关的知识,希望对你有一定的参考价值。
参考技术A AIMD英文全称:Additive Increase Multiplicative Decrease。TCP/IP模型中,属于[运输层],为了解决[拥塞控制]的一个方法,即:加性增,乘性减,或者叫做“和式增加,积式减少”。aimd controller是TCP底层的码率调节概念,但是WebRTC并没有完全照搬TCP的机制,而是设计了套自己的算法。
如果处于Incr状态,增加码率的方式分为两种:一种是通信会话刚刚开始,相当于TCP慢启动,它会进行一个倍数增加,当前使用的码率乘以系数,系数是1.08;如果是持续在通信状态,其增加的码率值是当前码率在一个RTT时间周期所能传输的数据速率。
如果处于Decrease状态,递减原则是:过去500ms时间窗内的最大acked bitrate乘上系数0.85,acked bitrate通过feedback反馈过来的报文序号查找本地发送列表就可以得到。
aimd根据上面的规则最终计算到的码率就是基于延迟拥塞评估到的bwe bitrate码率。
WebRTC在发送端收到来自接收端的RTCP RR报文,根据其Report Block中携带的丢包率信息,动态调整发送端码率As。
基于延迟的码率控制运行在接收端,WebRTC根据数据包到达的时间延迟,通过到达时间滤波器,估算出网络延迟m(t),然后经过过载检测器判断当前网络的拥塞状况,最后在码率控制器根据规则计算出远端估计最大码率Ar。然后,通过RTCP REMB报文返回Ar等到发送端。
发送端综合As、Ar和预配置的上下限,计算出最终的目标码率A,该码率会作用到Encoder、RTP和PacedSender等模块,控制发送端的码率。
GCC算法充分考虑丢包率和延迟对码率的影响,在实时通讯应用(如视频会议)中能够发挥良好效果。然而,在某些特定应用场景下(比如实时在线编辑),GCC算法的表现不太让人满意,主要体现在它应对峰值流量的能力上,具体表现在:
1)算法一开始基于Increase状态增加码率,当检测到Decrease状态时调用Ar[t(i)] = Alpha * Rr[t(i)],这个时候实时码率Rr(ti)可能远小于Ar[t(i-1)],这样在后续过程中Ar处于较低水平;此时若有视频关键帧冲击,则数据包大量在PacedSender的队列中排队,造成较大排队延迟。
2)基于1)中论述的情况,码率估计模块反馈给Codec的编码码率很低,但编码器需要编码关键帧时,内部的码率控制模块控制出的最小码率仍然大于反馈码率。这两种情况都会造成较大的发送端排队延迟,进而在接收端造成较大的JitterBuffer延迟,最终导致端到端延迟到达500ms的水平,这在实时在线编辑应用中是无法容忍的。
基于此,Google官方从WebRTC M55开始引入新的码率估计算法,把所有码率计算模块都移动到发送端,并采用全新的Trendline滤波器,基于码率探测机制快速准确地估计出实时码率。
WebRTC在评估延迟差的时候不是对每个包进行估算,而是采用了包组间进行延迟评估,这符合视频传输(视频帧是需要切分成多个UDP包)的特点,也减少了频繁计算带来的误差。那么什么是包组呢?就是距包组中第一个包的发送时刻t0小于5毫秒发送的所有的包成为一组,第一个超过5毫秒的包作为下一个包组第一个包。
Android端WebRTC本地音视频采集流程源码分析
WebRTC源码版本为:org.webrtc:google-webrtc:1.0.32006 本文仅分析Java层源码,在分析之前,先说明一下一些重要类的基本概念。
- MediaSource:WebRTC媒体资源数据来源,它有两个子类:AudioSource(音频资源)、VideoSource(视频资源);
- MediaStreamTrack:媒体资源轨,一个MediaStreamTrack对应一个MediaSource,创建媒体轨需要MediaSource,同样,它也有两个子类:AudioTrack(音频轨)对应AudioSource、VideoTrack(视频轨)对应VideoSource;
- MediaStream:媒体流,一个媒体流可以添加多条AudioTrack和VideoTrack,一般来说我们对应分别只添加一条。
在使用WebRTC进行音视频通话时,需要构建一个PeerConnectionFactory,这是一个连接工厂,创建本地LocalMediaStream流及客户端PeerConnection等,都需要使用。构建大致代码如下:
//在创建PeerConnectionFactory之前,必须至少调用一次。不得在 PeerConnectionFactory 处于活动状态时调用。
PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions
.builder(applicationContext).createInitializationOptions()
)
val eglBaseContext = EglBase.create().eglBaseContext
//视频编码工厂
val encoderFactory= DefaultVideoEncoderFactory(eglBaseContext, true, true)
//视频解码工厂
val decoderFactory = DefaultVideoDecoderFactory(eglBaseContext)
val audioDeviceModule = JavaAudioDeviceModule.builder(this)
.setSamplesReadyCallback audioSamples ->
//麦克风输入数据,即通话时LocalMediaStream的Audio的数据,pcm格式,通常用于录音。
.createAudioDeviceModule()
val peerConnectionFactory = PeerConnectionFactory.builder()
.setVideoEncoderFactory(encoderFactory)
.setVideoDecoderFactory(decoderFactory)
.setAudioDeviceModule(audioDeviceModule)
.createPeerConnectionFactory()
若你有需求WebRTC视频编码要启用H.264,请参考Android端WebRTC启用H264编码。
MediaSource
WebRTC媒体资源数据来源基类。
AudioSource(音频源)
通过 peerConnectionFactory.createAudioSource(MediaConstraints) 创建,参数为媒体约束,大致如下:
//音频源
val audioSource = peerConnectionFactory.createAudioSource(createAudioConstraints())
private fun createAudioConstraints(): MediaConstraints
val audioConstraints = MediaConstraints()
//回声消除
audioConstraints.mandatory.add(
MediaConstraints.KeyValuePair(
"googEchoCancellation",
"true"
)
)
//自动增益
audioConstraints.mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl", "true"))
//高音过滤
audioConstraints.mandatory.add(MediaConstraints.KeyValuePair("googHighpassFilter", "true"))
//噪音处理
audioConstraints.mandatory.add(
MediaConstraints.KeyValuePair(
"googNoiseSuppression",
"true"
)
)
return audioConstraints
事实上音频的输入、输出具体处理在JavaAudioDeviceModule:
package org.webrtc.audio;
/**
* AudioDeviceModule implemented using android.media.AudioRecord as input and
* android.media.AudioTrack as output.
*/
public class JavaAudioDeviceModule implements AudioDeviceModule
...
/**
* 本地麦克风采集的输入数据,android.media.AudioRecord
*/
private final WebRtcAudioRecord audioInput;
/**
* 用于播放通话时对方的音频数据,android.media.AudioTrack
*/
private final WebRtcAudioTrack audioOutput;
...
记录本地麦克风采集的音频数据WebRtcAudioRecord:
package org.webrtc.audio;
import android.media.AudioRecord;
class WebRtcAudioRecord
...
private AudioRecord audioRecord;
/**
* 读取线程
*/
private AudioRecordThread audioThread;
/**
* Audio thread which keeps calling ByteBuffer.read() waiting for audio
* to be recorded. Feeds recorded data to the native counterpart as a
* periodic sequence of callbacks using DataIsRecorded().
* This thread uses a Process.THREAD_PRIORITY_URGENT_AUDIO priority.
*/
private class AudioRecordThread extends Thread
private volatile boolean keepAlive = true;
public AudioRecordThread(String name)
super(name);
public void run()
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
//确保在采集数据状态
assertTrue(audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING);
//向外回调开始状态
doAudioRecordStateCallback(AUDIO_RECORD_START);
//存活状态
while(keepAlive)
int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity());
if (bytesRead == byteBuffer.capacity())
if (microphoneMute)
//如果麦克风静音,则清空数据
byteBuffer.clear();
byteBuffer.put(WebRtcAudioRecord.this.emptyBytes);
if (keepAlive)
//调用native方法发送数据
nativeDataIsRecorded(nativeAudioRecord, bytesRead);
//向外回调音频数据,pcm格式
if (audioSamplesReadyCallback != null)
byte[] data = Arrays.copyOfRange(.byteBuffer.array(), byteBuffer.arrayOffset(), byteBuffer.capacity() + byteBuffer.arrayOffset());
audioSamplesReadyCallback.onWebRtcAudioRecordSamplesReady(new AudioSamples(audioRecord.getAudioFormat(), audioRecord.getChannelCount(), audioRecord.getSampleRate(), data));
else
String errorMessage = "AudioRecord.read failed: " + bytesRead;
Logging.e("WebRtcAudioRecordExternal", errorMessage);
if (bytesRead ==AudioRecord.ERROR_INVALID_OPERATION)
keepAlive = false;
reportWebRtcAudioRecordError(errorMessage);
try
if (audioRecord != null)
audioRecord.stop();
//向外回调停止状态
doAudioRecordStateCallback(AUDIO_RECORD_STOP);
catch (IllegalStateException e)
Logging.e("WebRtcAudioRecordExternal", "AudioRecord.stop failed: " + e.getMessage());
/*
* 结束读取
*/
public void stopThread()
Logging.d("WebRtcAudioRecordExternal", "stopThread");
this.keepAlive = false;
...
播放接收的音频数据WebRtcAudioTrack(这里不属于采集流程,提了一嘴):
package org.webrtc.audio;
import android.media.AudioTrack;
class WebRtcAudioTrack
...
private AudioTrack audioTrack;
/**
* 读取线程
*/
private AudioTrackThread audioThread;
/**
* Audio thread which keeps calling AudioTrack.write() to stream audio.
* Data is periodically acquired from the native WebRTC layer using the
* nativeGetPlayoutData callback function.
* This thread uses a Process.THREAD_PRIORITY_URGENT_AUDIO priority.
*/
private class AudioTrackThread extends Thread
private volatile boolean keepAlive = true;
public AudioTrackThread(String name)
super(name);
public void run()
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
//确认状态
assertTrue(audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING);
//向外回调开始状态
doAudioTrackStateCallback(AUDIO_TRACK_START);
for(int sizeInBytes = byteBuffer.capacity(); keepAlive; byteBuffer.rewind())
//调用native方法将播放数据写入到byteBuffer中
nativeGetPlayoutData(nativeAudioTrack, sizeInBytes);
assertTrue(sizeInBytes <= byteBuffer.remaining());
if (speakerMute)
//扬声器静音,清空数据
byteBuffer.clear();
byteBuffer.put(emptyBytes);
byteBuffer.position(0);
//写入到AudioTrack中,进行播放,pcm格式数据
int bytesWritten = writeBytes(audioTrack, byteBuffer, sizeInBytes);
if (bytesWritten != sizeInBytes)
Logging.e("WebRtcAudioTrackExternal", "AudioTrack.write played invalid number of bytes: " + bytesWritten);
if (bytesWritten < 0)
keepAlive = false;
reportWebRtcAudioTrackError("AudioTrack.write failed: " + bytesWritten);
private int writeBytes(AudioTrack audioTrack, ByteBuffer byteBuffer, int sizeInBytes)
return VERSION.SDK_INT >= 21 ? audioTrack.write(byteBuffer, sizeInBytes, 0) : audioTrack.write(byteBuffer.array(), byteBuffer.arrayOffset(), sizeInBytes);
public void stopThread()
Logging.d("WebRtcAudioTrackExternal", "stopThread");
keepAlive = false;
...
上述的WebRtcAudioRecord和WebRtcAudioTrack两个类都不是public,在WebRTC中只对外暴露出了本地麦克风采集数据,未暴露音频输出数据,如果你有获取WebRTC通话接收到的音频数据需求,请参考Android端WebRTC音视频通话录音-获取音频输出数据。
VideoSource(视频源)
VideoSource是一个视频源,用于二次处理视频数据(VideoProcessor)和调整大小、格式和发送视频数据(具体实现是jni的包装类NativeAndroidVideoTrackSource)。 VideoSource视频的获取需要通过CapturerObserver,视频捕捉器可以通过videoSource.getCapturerObserver()获取捕捉器观察者,将数据传给VideoSource。 先看一下VideoSource创建使用流程:
//视频捕捉器
val videoCapturer = createVideoCapture(context)
videoCapturer?.let capturer->
val videoSource = peerConnectionFactory.createVideoSource(capture.isScreencast)
//使用 SurfaceTexture 创建 WebRTC VideoFrames 的帮助类。为了创建 WebRTC VideoFrames,渲染到 SurfaceTexture。帧将传递给侦听器。
val surfaceTextureHelper =
SurfaceTextureHelper.create("surface_texture_thread", eglBaseContext)
//将surfaceTextureHelper和CapturerObserver传给VideoCapturer
capture.initialize(surfaceTextureHelper, context, videoSource.capturerObserver)
/**
* 创建相机视频捕捉器
*/
private fun createVideoCapture(context: Context): CameraVideoCapturer?
val enumerator: CameraEnumerator = if (Camera2Enumerator.isSupported(context))
Camera2Enumerator(context)
else
Camera1Enumerator()
for (name in enumerator.deviceNames)
if (enumerator.isFrontFacing(name))
return enumerator.createCapturer(name, null)
for (name in enumerator.deviceNames)
if (enumerator.isBackFacing(name))
return enumerator.createCapturer(name, null)
return null
我们来看一下VideoSource关键代码:
package org.webrtc;
public class VideoSource extends MediaSource
private final NativeAndroidVideoTrackSource nativeAndroidVideoTrackSource;
/**
* 视频处理器,可自行对图像数据进行二次处理;
* 如:添加水印、视频旋转、剪裁等。
*/
@Nullable
private VideoProcessor videoProcessor;
private final Object videoProcessorLock = new Object();
private final CapturerObserver capturerObserver = new CapturerObserver()
/**
* 开始捕捉
*/
@Override
public void onCapturerStarted(boolean success)
nativeAndroidVideoTrackSource.setState(success);
synchronized(videoProcessorLock)
isCapturerRunning = success;
if (videoProcessor != null)
videoProcessor.onCapturerStarted(success);
/**
* 停止捕捉
*/
@Override
public void onCapturerStopped()
nativeAndroidVideoTrackSource.setState(false);
synchronized(videoProcessorLock)
isCapturerRunning = false;
if (videoProcessor != null)
videoProcessor.onCapturerStopped();
/**
* 捕捉的视频数据
* @param frame 视频数据
*/
@Override
public void onFrameCaptured(VideoFrame frame)
//应在传送任何帧之前调用此函数,以确定是否应丢弃该帧或裁剪、缩放参数是什么。如果FrameAdaptationParameters#drop为true,则应丢弃该帧,否则应在调用 onFrameCaptured() 之前根据帧适配参数对帧进行适配。
FrameAdaptationParameters parameters = nativeAndroidVideoTrackSource.adaptFrame(frame);
synchronized(videoProcessorLock)
if (videoProcessor != null)
//如果添加了视频处理器,自行处理,直接return。
videoProcessor.onFrameCaptured(frame, parameters);
return;
//根据参数调整帧数据,此步骤会调用VideoFrame.getBuffer().cropAndScale()进行裁剪缩放。
VideoFrame adaptedFrame = VideoProcessor.applyFrameAdaptationParameters(frame, parameters);
if (adaptedFrame != null)
//如果不为空,则通过native方法将帧数据传递过去
nativeAndroidVideoTrackSource.onFrameCaptured(adaptedFrame);
adaptedFrame.release();
;
/**
* 调整输出格式
*/
public void adaptOutputFormat(int width, int height, int fps)
int maxSide = Math.max(width, height);
int minSide = Math.min(width, height);
this.adaptOutputFormat(maxSide, minSide, minSide, maxSide, fps);
public void adaptOutputFormat(int landscapeWidth, int landscapeHeight, int portraitWidth, int portraitHeight, int fps)
this.adaptOutputFormat(new VideoSource.AspectRatio(landscapeWidth, landscapeHeight), landscapeWidth * landscapeHeight, new VideoSource.AspectRatio(portraitWidth, portraitHeight), portraitWidth * portraitHeight, fps);
public void adaptOutputFormat(VideoSource.AspectRatio targetLandscapeAspectRatio, @Nullable Integer maxLandscapePixelCount, VideoSource.AspectRatio targetPortraitAspectRatio, @Nullable Integer maxPortraitPixelCount, @Nullable Integer maxFps)
nativeAndroidVideoTrackSource.adaptOutputFormat(targetLandscapeAspectRatio, maxLandscapePixelCount, targetPortraitAspectRatio, maxPortraitPixelCount, maxFps);
/**
* 设置视频处理器
*/
public void setVideoProcessor(@Nullable VideoProcessor newVideoProcessor)
synchronized(videoProcessorLock)
//判断之前是否设置过
if (videoProcessor != null)
videoProcessor.setSink(null);
if (isCapturerRunning)
videoProcessor.onCapturerStopped();
videoProcessor = newVideoProcessor;
if (newVideoProcessor != null)
newVideoProcessor.setSink(new VideoSink()
@Override
public void onFrame(VideoFrame frame)
//这里返回的是自行处理好的数据,再通过native方法将帧数据传递过去,和上面一样。
runWithReference(() ->
nativeAndroidVideoTrackSource.onFrameCaptured(frame);
);
);
if (isCapturerRunning)
newVideoProcessor.onCapturerStarted(true);
/**
* 获取捕捉器观察者
*/
public CapturerObserver getCapturerObserver()
以上是关于WEBRTC基本概念的主要内容,如果未能解决你的问题,请参考以下文章
零基础快速入门WebRTC:基本概念关键技术与WebSocket的区别等