首先,打开videocapture对象,使用如下代码
navigator.webkitGetUserMedia({video:true}, gotStream, function() {});
参数依次是:localmedia的配置信息,得到流的回调函数,出现错误时的回调函数。
gotStream的实现是
function gotStream(stream) {
video.src = webkitURL.createObjectURL(stream);
}
video是一个video标签。
stream是获取的stream对象。通过createObjectURL将其转换为url对象,传递给video,实现将流内容显示在video标签上。
数据来自与android的Camera对象,获取的是front摄像头。 由于android的Camera必须设置一个Surface或者SurfaceTexture作为预览的目标,否则,将不能获取任何数据。因此,我在内部创建了一个隐藏的SurfaceTexture对象,唯一的目的是让Camera能够获取数据。
Camera获取的是NV21格式(YCrCb420SP),需要转换为YV12(YCrCb420P)。
在渲染时,数据格式被分为Y U V 3个面(Pane),每个面对应一个Texture,因此,共有3个Texture, 将这3个Texture混合在一起(通过YUV2RGB的混合矩阵),得到RGB数据,并被显示在GL的FrameBuffer中。
以上过程,仅仅将各个模块的关键点给出,省略了很多次要过程。
实际上,上述过程设计两个进程:Render和Host进程。 其中的CC(Component Composite) 在Render进程,而GPUHost则在Host进程,它们都在独立线程中运行。
事件通知使用了4种方式:
localmedia的实现是非常庞大而复杂的,线索众多并相互交织。因此,我们必须从上至下,一层一层的进行分析,才能很好的解析清楚。
这是LocalMedia的基本原理。
中间部分,是LocalMedia的核心管理部分。
它的基本流程是:
WebCore层有两个主要数据:
WebCore::MediaConstraints由WebCore::MediaConstraintsImpl,这是一个简单的数据结构,它将传递的数据结构转换为一个hash表保存。
WebCore::MediaStreamDescriptor稍微复杂些,它的层次结构如下:
该数据结构中,重要的数据结构,实际上是MediaStreamSource:ExtraData和MediaStreamDescriptor::ExtraData.
因为,在WebCore这一层,完全不管理具体的音频、视频流,它仅仅管理了两个source,所有和真正Source实现部分,都是保存在MediaStreamSource::ExtraData中的。
而和Render相关的数据,则保存在MediaStreamDescriptor::ExtraData中。
在js中,得到的stream对象,在C++层面,就是WebCore::MediaStreamDescriptor对象。
由于video标签的src属性,只接收URL,因此,无法直接将WebCore::MediaStreamDescriptor对象传递给video标签,所以,使用createObjectURL。
createObjectURL的实现很简单,就是利用特定算法,将WebCore::MediaStreamDescriptor对象计算为一个唯一的字符串,以此字符串为Key,放在hash表中映射WebCore::MediaStreamDescriptor对象。
关于具体实现,可以查阅源代码。
以下接口是核心部分(Audo部分的接口参考Video的接口)
代码摘要:
// Interface for rendering VideoFrames from a VideoTrack
class VideoRendererInterface {
public:
virtual void SetSize(int width, int height) = 0;
virtual void RenderFrame(const cricket::VideoFrame* frame) = 0;protected:
// The destructor is protected to prevent deletion via the interface.
// This is so that we allow reference counted classes, where the destructor
// should never be public, to implement the interface.
virtual ~VideoRendererInterface() {}
};
代码摘要
// VideoSourceInterface is a reference counted source used for VideoTracks.
// The same source can be used in multiple VideoTracks.
// The methods are only supposed to be called by the PeerConnection
// implementation.
class VideoSourceInterface : public MediaSourceInterface {
public:
// Get access to the source implementation of cricket::VideoCapturer.
// This can be used for receiving frames and state notifications.
// But it should not be used for starting or stopping capturing.
virtual cricket::VideoCapturer* GetVideoCapturer() = 0;
// Adds |output| to the source to receive frames.
virtual void AddSink(cricket::VideoRenderer* output) = 0;
virtual void RemoveSink(cricket::VideoRenderer* output) = 0;protected:
virtual ~VideoSourceInterface() {}
};
这里引入的cricket::VideoRenderer接口和webrtc::VideoRendererInterface接口实际上是一样的。
chromium在内部,将对VideoRenderer的调用,转换为对VideoRendererInterface的调用。
之所以提供两个不同的接口,是因为cricket::VideoRenderer是webrtc的第三方库提供的。
该类不是一个纯虚类,而是有部分纯虚函数。下面贴出其纯虚函数:
class VideoCapturer
: public sigslot::has_slots<>,
public talk_base::MessageHandler {
public:
// All signals are marshalled to |thread| or the creating thread if
// none is provided.
VideoCapturer();
explicit VideoCapturer(talk_base::Thread* thread);
virtual ~VideoCapturer() {}......
virtual CaptureState Start(const VideoFormat& capture_format) = 0;
// Stop the video capturer.
virtual void Stop() = 0;
// Check if the video capturer is running.
virtual bool IsRunning() = 0;....
};
代码摘要
class VideoTrackInterface : public MediaStreamTrackInterface {
public:
// Register a renderer that will render all frames received on this track.
virtual void AddRenderer(VideoRendererInterface* renderer) = 0;
// Deregister a renderer.
virtual void RemoveRenderer(VideoRendererInterface* renderer) = 0;
// Gets a pointer to the frame input of this VideoTrack.
// The pointer is valid for the lifetime of this VideoTrack.
// VideoFrames rendered to the cricket::VideoRenderer will be rendered on all
// registered renderers.
virtual cricket::VideoRenderer* FrameInput() = 0;
virtual VideoSourceInterface* GetSource() const = 0;
protected:
virtual ~VideoTrackInterface() {}
};
VideoTrackInterface是一个内部使用的接口,这个接口主要负责将其他接口链接起来。
可以用下图来描述(仅表示其逻辑关系,不代表代码真实情况。这种逻辑关系,由他们的实现类体现出来)
核心管理的两个接口是:
其中VideoSourceInterface负责管理Capturer和Renderer,将两者的数据贯穿起来;
而VideoTrackInterface的作用,是将VideoRenderererInterface转换为VideoRenderer接口。而且,将对一个VideoRenderer的调用,转换为对多个VideoRendererInterface的调用。
本节介绍几个重点类。这些类是上节提到的几个接口的实现类,以及他们和MediaStreamDescriptor如何组合在一起的。这种关系,是实现localmedia的核心所在。
图例说明:
LocalMedia的层次大约有WebCore、WebKit和content三个层次,其中,content又分为render和host两个进程。
WebCore层次主要提供面向javascript和标签的接口;content主要提供具体实现;WebKit层其实是给WebCore层穿了个马甲,隔离WebCore和content层。
我们重点分析的是WebCore和content层。
整个核心,有3个类:
UserMediaClient和MediaStreamCenter是必须由外部提供和实现的。
该图按照3个层次划分了各个类。其中最重要的是以下3个类
对于真实设备的管理,是整个Localmedia的核心。在这一节中,我们重点讨论对真实的Video设备的管理。这些全部的内容,都在content层实现的。
这部分管理分为两部分:
这两类消息是通过不同的方法实现的。
它有3个重要的数据结构
它有两个数据结构,其中一个,是MediaStreamDevice;另外一个,是 int session_id。这个session_id,代表了一个具体设备,是用于和Host端通讯使用的。
接口cricket::VideoCapturer和media::VideoCapture(注意,一个后有"r",一个没有)。这两个对象,实际上是一回事,但是针对的不同的层次。cricket::VideoCapturer主要针对webrtc和上层关系,而media::VideoCapture主要针对Host,和Host进行通讯。
它们的关系很密切,如下图:
MediaStreamImpl直接包含了MediaStreamDispatcher类。 MediaStreamDispatcher类通过MediaStreamDispatcherEventHandler将Host的回馈消息发送给MediaStreamImpl。
MediaStreamImpl通过UserMediaRequestInfo保存对UserMediaRequest的引用。
当javascript代码调用webkitGetUserMedia的时候,
media::VideoCaptureDevice是代表一个视频设备的类。
整个过程分为两个部分:
中间有若干部分都是异步的。
Render进程的消息,有两个关键类来管理 content::VideoCaptureImpl和content::VideoCaptureMessageFilter。这两个类,其中VideoCaptureImpl负责发送消息,而VideoCaptureMessageFilter负责接收消息。
消息的传递和接收,必须严格在IO线程内部。而对capture的所有处理,应该在capture线程。
IO线程来自与ChildProcess,而对应的message_loop_proxy就是 io_message_loop_proxy_
Capture线程来自 VideoCaptureManagerImpl的thread_,对应的message_loop_proxy就是 capture_message_loop_proxy_
VideoCaptureImplManager对象隶属于RenderThreadImpl类,并将VideoCaptureMessageFilter添加到RenderThreadImpl的Filter当中。只有添加到RenderThreadImpl的filter中,VideoCaptureMessageFilter才能接收到来自Host的消息。
Host和Render在传送消息时,通过SessionID(有的类里称为DeviceID,或者是DeviceControllerID等)来标示一个唯一的设备。
这一节,我们将探讨Shred Buffer的创建和使用的相关细节。
在Render端,Shared Buffer由VideoCaptureImpl管理;在Host端,SharedBuffer由VideoCaptureController管理。
VideoCaptureImpl:: DIBBuffer对象和VideoCaptureController::SharedDIB 两个类的定义完全一样。
核心是对象base::SharedMemory对象,它负责将创建并管理共享内存。
SharedBuffer的创建过程是从VideoCaptureDevice对象的Allocate方法开始的。
Allocate方法,是要求VideoCapture创建设备,并为止分配足够的空间。
WebMediaPlayerMS派生自WebKit::WebMediaPlayer,它给HTMLVideoElement类提供一个Player对象。
WebMediaPlayerMS在函数RenderViewImpl::createMediaPlayer中被创建。
WebMediaPlayerMS表示获取并播放流对象。WindowMediaPlayer有几个重要的函数:
RTCVideoRenderer一方面实现了接口VideoRendererInterface,从而能够接收来自本地/远程的视频流,另外一方面,实现了VideoFrameProvider接口,能够将这种数据传递给WebMediaPlayerMS。
RTCVideoRender通过回调函数WebMediaPlayerMS::VideoFrameProvider
WebMediaPlayerMS在得到数据后,调用Repaint,该函数最终会引起CC层的重新调度。在调度时,VideoLayerImpl就会调用getCurrentFrame获取当前需要绘制的视频帧。
在CC层的处理下,数据最终被绘制到了屏幕上。
VideoLayerImpl和一个video标签是对应的,因此,它能够获得video标签的位置并绘制。
视频数据是YV12格式(详细参阅 YUV http://zh.wikipedia.org/wiki/YUV)。从 android的获得的NV21。
YV12实际上YUV420P,而NV21则是YUV420SP。 ‘S’的含义是交错格式。即U分量和V分量交错存储。P为Plane,表示一个平面。YUV有3个Plane,分别是Y、U(Cb)、V(Cr)
YUV 也可以写为YCbCr
所谓420,含义4个Y,共有一个UV。 即, Y00, Y10, Y01, Y11 共有U00, V00。
YV12的示意图
YU12和YV12属于YUV420格式,也是一种Plane模式,将Y、U、V分量分别打包,依次存储。其每一个像素点的YUV数据提取遵循YUV420格式的提取方式,即4个Y分量共用一组UV。注意,上图中,Y'00、Y'01、Y'10、Y'11共用Cr00、Cb00,其他依次类推。
NV21的示意图
NV12和NV21属于YUV420格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane。其提取方式与上一种类似,即Y'00、Y'01、Y'10、Y'11共用Cr00、Cb00
表示YUV数据的对象,是VideoFrame对象,但是,在不同的阶段,相同的数据,数据对象类却是不一样的。为了防止造成误解,我把从VideoCapture到MediaPlayer这个过程中,涉及的数据格式和类,用图表表示出来,如下:
左边为设备管理对象,虚线表示函数调用;右边为数据对象,箭头线表示数据转换的方向和方法。
其中,
Chromium暴露给OS层的接口,并要求OS层实现的接口,仅有media::VideoCaptureDevice。只要实现该接口,就能实现对视频设备的支持。