WebRTC源码初探之Android平台视频采集

WebRTC源码初探之Android平台视频采集

WebRTC的采集模块中提供了多个平台的音视频采集实现,这里我们针对Android平台来分析一下WebRTC的采集模块。

在讲解之前有几个概念需要明白

  • Source: 在WebRTC中将一切数据的来源都抽象成一个Source
  • Track: 在WebRTC中一个Track代表一路音频或视频,一个Track需要通过一个Source来创建
  • Sink: 在WebRTC中将数据的消费都抽象成一个Sink,将一个Sink添加到一个Track上,就可以收到对应Source的数据

VideoCapturer

VideoCapturer是设备采集模块对外提供的控制接口,通过CameraEnumerator来创建。

通过CameraEnumerator可以检测当前设备上Camera设备的支持情况,然后通过createCapturer函数可以开启指定Camera设备进行采集,VideoCapturer对采集提供一些逻辑控制。

1
2
3
4
5
6
private static VideoCapturer createVideoCapturer(Context context) {
CameraEnumerator enumerator = Camera2Enumerator.isSupported(context)
? new Camera2Enumerator(context)
: new Camera1Enumerator();
return enumerator.createCapturer(enumerator.getDeviceNames()[0], null /* eventsHandler */);
}

CameraEnumerator

CameraEnumerator负责提供对Camera设备信息的获取、以及负责创建CameraCapturer,具体实现有Camera1Enumerator和Camera2Enumerator。

CameraCapturer

CameraCapturer是对采集操作的封装,对外提供了开始采集、结束采集、设置采集参数等接口,负责创建CameraSession,对CameraSession的功能进行接口封装。CameraCapturer的具体实现有Camera1Capturer和Camera2Capturer。

CameraSession

CameraSession是对AndroidCameraApi的包装,具体实现有Camera1Session和Camera2Session。

数据类型

CameraApi支持两种类型的数据回调,一种是字节流,另一种是纹理。这两种类型适合不同的场景,字节流类型对于FFmpeg等软编码比较方便,但是如果想要使用OpenGL-ES来对数据进行处理,那么就需要将字节流转换成纹理才行;纹理类型对OpenGL-ES处理比较方便,但是只有MediaCodec才支持纹理的编码,如果想要使用FFmpeg软编码,需要将纹理数据提取成字节流。

Camera1Session两种类型的数据都支持,通过参数captureToTexture来控制返回的数据类型。

1
2
3
public Camera1Enumerator(boolean captureToTexture) {
this.captureToTexture = captureToTexture;
}

Camera2Session只支持纹理类型的数据返回,并没有实现Camera2的字节流数据返回。(Camera2可以通过ImageReader获取字节流,但是硬件兼容性太差,不同厂商实现不一致)

VideoFrame

WebRTC将一帧视频数据封装成一个VideoFrame,一帧视频记录三个值,视频帧的方向、时间、数据buffer。其中Buffer有两种,对应着两种数据类型。

1
2
3
4
5
public class VideoFrame implements RefCounted {
private final Buffer buffer; // 数据buffer
private final int rotation; // 方向
private final long timestampNs; // 时间戳
}

I420Buffer

字节流类型buffer。字节流类型只支持I420格式的数据,在Buffer中分别保存了Y、U、V三个ByteBuffer。其实现类为JavaI420Buffer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface I420Buffer extends Buffer {

@CalledByNative("I420Buffer") ByteBuffer getDataY();

@CalledByNative("I420Buffer") ByteBuffer getDataU();

@CalledByNative("I420Buffer") ByteBuffer getDataV();

@CalledByNative("I420Buffer") int getStrideY();
@CalledByNative("I420Buffer") int getStrideU();
@CalledByNative("I420Buffer") int getStrideV();
}

public class JavaI420Buffer implements VideoFrame.I420Buffer {

private final int width;
private final int height;
private final ByteBuffer dataY;
private final ByteBuffer dataU;
private final ByteBuffer dataV;
private final int strideY;
private final int strideU;
private final int strideV;
private final RefCountDelegate refCountDelegate;
}

TextureBuffer、

纹理类型buffer。对于纹理在Android上有两种类型,一种是通用的RGB纹理,另一种是扩展的OES纹理(OES纹理支持跨线程使用),Android系统API使用的都是OES纹理。对于纹理来说,需要保存纹理类型、纹理ID、变换矩阵。其实现类为TextureBufferImpl。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public interface TextureBuffer extends Buffer {
enum Type {
OES(GLES11Ext.GL_TEXTURE_EXTERNAL_OES),
RGB(GLES20.GL_TEXTURE_2D);

private final int glTarget;

private Type(final int glTarget) {
this.glTarget = glTarget;
}

public int getGlTarget() {
return glTarget;
}
}

Type getType();
int getTextureId();

Matrix getTransformMatrix();
}

public class TextureBufferImpl implements VideoFrame.TextureBuffer {

// This is the full resolution the texture has in memory after applying the transformation matrix
// that might include cropping. This resolution is useful to know when sampling the texture to
// avoid downscaling artifacts.
private final int unscaledWidth;
private final int unscaledHeight;
// This is the resolution that has been applied after cropAndScale().
private final int width;
private final int height;
private final Type type;
private final int id;
private final Matrix transformMatrix;
private final Handler toI420Handler;
private final YuvConverter yuvConverter;
private final RefCountDelegate refCountDelegate;
}

VideoSource

Java层API

前面说过Source代表一个数据源,Source可以生成一个监听(videoSource.getCapturerObserver())设置到Capturer中来获取数据。

1
2
3
4
5
6
// 通过工厂方法创建VideoSource
videoSource = factory.createVideoSource(capturer.isScreencast());
// 注册数据监听
capturer.initialize(surfaceTextureHelper, appContext, videoSource.getCapturerObserver());
// 开始捕捉
capturer.startCapture(videoWidth, videoHeight, videoFps);

我们来看一下VideoSource中CapturerObserver是如何处理的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private final CapturerObserver capturerObserver = new CapturerObserver() {
@Override
public void onCapturerStarted(boolean success) {
// 开始捕捉
}

@Override
public void onCapturerStopped() {
// 停止捕捉
}

@Override
public void onFrameCaptured(VideoFrame frame) {
// 对数据进行预处理
VideoFrame adaptedFrame = VideoProcessor.applyFrameAdaptationParameters(frame, parameters);
if (adaptedFrame != null) {
// 将数据传递到Native层的VideoSource
nativeAndroidVideoTrackSource.onFrameCaptured(adaptedFrame);
adaptedFrame.release();
}
}
};

Native层API

Native层的VideoSource接口叫VideoTrackSourceInterface,其实现类为JavaVideoTrackSourceImpl。

1
2
3
4
// 通过静态工厂方法创建VideoSource
video_source_ = webrtc::CreateJavaVideoSource(env, signaling_thread_.get(),
/* is_screencast= */ false,
/* align_timestamps= */ true);

在JavaVideoTrackSourceImpl中,会创建一个AndroidVideoTrackSource对象,这个对象负责具体的数据接收和分发。通过CreateJavaNativeCapturerObserver创建Java层的
NativeCapturerObserver对象,将AndroidVideoTrackSource放入到NativeCapturerObserver中。

通过GetJavaVideoCapturerObserver可以获得刚创建的NativeCapturerObserver对象,这是一个Java对象,可以直接交给Java层的VideoCapturer,在initialize时注入到VideoCapturer中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class JavaVideoTrackSourceImpl : public JavaVideoTrackSourceInterface {
public:
JavaVideoTrackSourceImpl(JNIEnv* env,
rtc::Thread* signaling_thread,
bool is_screencast,
bool align_timestamps)
: android_video_track_source_(
new rtc::RefCountedObject<jni::AndroidVideoTrackSource>(
signaling_thread,
env,
is_screencast,
align_timestamps)),
native_capturer_observer_(jni::CreateJavaNativeCapturerObserver(
env,
android_video_track_source_)) {}

ScopedJavaLocalRef<jobject> GetJavaVideoCapturerObserver(
JNIEnv* env) override {
return ScopedJavaLocalRef<jobject>(env, native_capturer_observer_);
}

在NativeCapturerObserver中维护的NativeAndroidVideoTrackSource就是Native层的AndroidVideoTrackSource对象,当onFrameCaptured触发的时候,会调用Native对象的onFrameCaptured。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class NativeCapturerObserver implements CapturerObserver {
// Native层的AndroidVideoTrackSource对象
private final NativeAndroidVideoTrackSource nativeAndroidVideoTrackSource;

@CalledByNative
public NativeCapturerObserver(long nativeSource) {
this.nativeAndroidVideoTrackSource = new NativeAndroidVideoTrackSource(nativeSource);
}

@Override
public void onCapturerStarted(boolean success) {
nativeAndroidVideoTrackSource.setState(success);
}

@Override
public void onCapturerStopped() {
nativeAndroidVideoTrackSource.setState(/* isLive= */ false);
}

@Override
public void onFrameCaptured(VideoFrame frame) {
final VideoProcessor.FrameAdaptationParameters parameters =
nativeAndroidVideoTrackSource.adaptFrame(frame);
if (parameters == null) {
// Drop frame.
return;
}
// 预处理帧
final VideoFrame.Buffer adaptedBuffer =
frame.getBuffer().cropAndScale(parameters.cropX, parameters.cropY, parameters.cropWidth,
parameters.cropHeight, parameters.scaleWidth, parameters.scaleHeight);
// 将数据通知到Native层
nativeAndroidVideoTrackSource.onFrameCaptured(
new VideoFrame(adaptedBuffer, frame.getRotation(), parameters.timestampNs));
adaptedBuffer.release();
}
}

VideoTrack

一个Track代表一个音频或视频轨道,这里是创建一个视频轨道,需要指定视频源。

Java层API
1
2
// 通过VideoSource创建VideoTrack
localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
Native层API
1
2
3
// 通过VideoSource创建VideoTrack
rtc::scoped_refptr<webrtc::VideoTrackInterface> local_video_track =
pcf_->CreateVideoTrack("video", video_source_);

VideoSink

一个Sink代表一个消费者,负责消费一个Track上的数据。

Java层API

将一个Sink的实例添加到Track上,那么当Track上的Source获取到数据后,就会调用Sink的onFrame函数,将数据传过来。

1
localVideoTrack.addSink(localRender);

VideoSink只定义了一个函数,这个函数是给Native层调用的,Native层的Track会通过JNI调用Sink的onFrame方法,将VideoFrame数据传过来。

1
2
3
4
public interface VideoSink {

@CalledByNative void onFrame(VideoFrame frame);
}

VideoSink的实现类有VideoFileRenderer、SurfaceViewRenderer、SurfaceEglRenderer、AndroidVideoDecoder等,我们可以想到,需要消费视频数据的有两种目的,一种是使用视频数据来进行渲染显示,另一种是使用视频数据来进行编码。

  • SurfaceViewRenderer就是基于SurfaceView来实现的Sink,可以直接将接收到的数据显示到SurfaceView上。
  • AndroidVideoDecoder就是基于MediaCodec来实现的Sink,可以直接将接收到的数据送到MediaCodec中进行编码。
  • VideoFileRenderer是直接将视频数据写入到文件中的Sink。
Native层API

对于Native层的Sink,其接口是VideoSinkInterface,有几个主要实现

  • VideoSinkWrapper:这个是对Java层的Sink进行包装。
  • VideoStreamEncoder:这个是负责编码处理的Sink。
  • VideoObserver:这个可以通过设置callback获取到Sink收到的数据。
1
2
3
4
// 使用Java层的Sink进行包装,最终会通过JNI调用到Java层Sink的OnFrame方法
local_sink_ = webrtc::JavaToNativeVideoSink(env, local_sink.obj());

local_video_track->AddOrUpdateSink(local_sink_.get(), rtc::VideoSinkWants());

通过VideoTrack的添加方法,我们可以看到,添加一个Sink到Track上,实际上是添加到Source上。

1
2
3
4
5
6
7
8
9
void VideoTrack::AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink,
const rtc::VideoSinkWants& wants) {
RTC_DCHECK(worker_thread_->IsCurrent());
VideoSourceBase::AddOrUpdateSink(sink, wants);
rtc::VideoSinkWants modified_wants = wants;
modified_wants.black_frames = !enabled();
// 将Sink添加到Source上
video_source_->AddOrUpdateSink(sink, modified_wants);
}

上面讲过,VideoSource的实现类是JavaVideoTrackSourceImpl,最终会调用到其内部的AndroidVideoTrackSource对象,AndroidVideoTrackSource对象使用VideoBroadcaster专门用来管理Sink。

1
2
3
4
5
6
7
8
9
10
11
12
13
void VideoBroadcaster::AddOrUpdateSink(
VideoSinkInterface<webrtc::VideoFrame>* sink,
const VideoSinkWants& wants) {
RTC_DCHECK(sink != nullptr);
rtc::CritScope cs(&sinks_and_wants_lock_);
if (!FindSinkPair(sink)) {
// |Sink| is a new sink, which didn't receive previous frame.
previous_frame_sent_to_all_sinks_ = false;
}
// 将sink添加到内部的数组中
VideoSourceBase::AddOrUpdateSink(sink, wants);
UpdateWants();
}

上面讲Source的时候讲过,会将AndroidVideoTrackSource包装成Java层的Observer,交给Java层的VideoCapturer,然后当数据产生时,会调用到Native层的OnFrameCaptured进行通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void AndroidVideoTrackSource::OnFrameCaptured(
JNIEnv* env,
jint j_rotation,
jlong j_timestamp_ns,
const JavaRef<jobject>& j_video_frame_buffer) {
// 将Java层的VideoFrame转换成Native层的VideoFrame
rtc::scoped_refptr<VideoFrameBuffer> buffer =
AndroidVideoBuffer::Create(env, j_video_frame_buffer);
const VideoRotation rotation = jintToVideoRotation(j_rotation);

// AdaptedVideoTrackSource handles applying rotation for I420 frames.
if (apply_rotation() && rotation != kVideoRotation_0)
buffer = buffer->ToI420();

OnFrame(VideoFrame::Builder()
.set_video_frame_buffer(buffer)
.set_rotation(rotation)
.set_timestamp_us(j_timestamp_ns / rtc::kNumNanosecsPerMicrosec)
.build());
}

在数据产生时,会通知到VideoBroadcaster,根据添加的Sink集合,调用Sink的OnFrame方法对数据进行下发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void VideoBroadcaster::OnFrame(const webrtc::VideoFrame& frame) {
rtc::CritScope cs(&sinks_and_wants_lock_);
bool current_frame_was_discarded = false;
// 遍历所有Sink
for (auto& sink_pair : sink_pairs()) {
if (sink_pair.wants.rotation_applied &&
frame.rotation() != webrtc::kVideoRotation_0) {
// Calls to OnFrame are not synchronized with changes to the sink wants.
// When rotation_applied is set to true, one or a few frames may get here
// with rotation still pending. Protect sinks that don't expect any
// pending rotation.
RTC_LOG(LS_VERBOSE) << "Discarding frame with unexpected rotation.";
sink_pair.sink->OnDiscardedFrame();
current_frame_was_discarded = true;
continue;
}
if (sink_pair.wants.black_frames) {
webrtc::VideoFrame black_frame =
webrtc::VideoFrame::Builder()
.set_video_frame_buffer(
GetBlackFrameBuffer(frame.width(), frame.height()))
.set_rotation(frame.rotation())
.set_timestamp_us(frame.timestamp_us())
.set_id(frame.id())
.build();
sink_pair.sink->OnFrame(black_frame);
} else if (!previous_frame_sent_to_all_sinks_ && frame.has_update_rect()) {
// Since last frame was not sent to some sinks, no reliable update
// information is available, so we need to clear the update rect.
webrtc::VideoFrame copy = frame;
copy.clear_update_rect();
sink_pair.sink->OnFrame(copy);
} else {
sink_pair.sink->OnFrame(frame);
}
}
previous_frame_sent_to_all_sinks_ = !current_frame_was_discarded;
}

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×