MediaStream API 是连接 WebRTC API 和底层物理流的中间层, webRTC 将音视频经过Vocie / Video engine进行处理后, 再通过 MediaStream API 给暴露给上层使用。

一个 MediaStream 对象包含零个或更多的 MediaStreamTrack 对象, 代表着各种的声轨和视频轨. 每一个 MediaStreamTrack 可能有一个或更多的通道. 这个通道代表着媒体流的最小单元, 比如一个音频信号对应着一个对应的扬声器, 像是在立体声音轨中的左通道或右通道。MediaTrack由 source 与 sink 组成. WebRTC并不能直接访问或者控制源, 对源的一切控制都可以通过轨道控制 MediaTrackConstraints 进行实施
MediaStreamTrack
MediaStreamTrack 是 WebRTC 中的基本媒体单位, 一个 MediaStreamTrack 包含一种媒体源(媒体设备或录制内容)返回的单一类型的媒体(如音频,视频).单个轨道可包含多个通道, 如立体声源尽管由多个音频轨道构成, 但也可以看作是一个轨道
MediaStream
MediaStream 是 MediaStreamTrack 的合集, 可以包含 >=0 个 MediaStreamTrack . MediaStream 能够确保它所包含的所有轨道都是是同时播放的, 以及轨道的单一性
source 与 sink
在 MediaTrack 的源码中, MediaTrack 都是由对应的 source 和 sink 组成的
//src\pc\video_track.cc
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();
video_source_->AddOrUpdateSink(sink, modified_wants);
}
void VideoTrack::RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) {
RTC_DCHECK(worker_thread_->IsCurrent());
VideoSourceBase::RemoveSink(sink);
video_source_->RemoveSink(sink);
}
浏览器中存在从 source 到 sink 的媒体管道, 其中 source 负责生产媒体资源, 包括多媒体文件, web 资源等静态资源以及麦克风采集的音频, 摄像头采集的视频等动态资源。而sink则负责消费source生产媒体资源, 也就是通过video 等媒体标签进行展示, 或者是通过 RTCPeerConnection 将 source 通过网络传递到远端。 RTCPeerConnection 可同时扮演 source 与 sink 的角色,作为 sink, 可以将获取的 source 降低码率, 缩放, 调整帧率等, 然后传递到远端, 作为 source, 将获取的远端码流传递到本地渲染
MediaTrackConstraints
MediaTrackConstraints 描述 MediaTrack 的功能以及每个功能可以采用的一个或多个值, 从而达到选择和控制源的目的 MediaTrackConstraints 可作为参数传递给 applyConstraints() 以达到控制轨道属性的目的, 同时可以通过调 getConstraints() 用来查看最近应用自定义约束
const constraints = {
width: {min: 640, ideal: 1280},
height: {min: 480, ideal: 720},
advanced: [
{width: 1920, height: 1280},
{aspectRatio: 1.333}
]
};
// { video: true }也是一个MediaTrackConstraints对象,用于指定请求的媒体类型和相对应的参数。
navigator.mediaDevices.getUserMedia({ video: true })
.then(mediaStream => {
const track = mediaStream.getVideoTracks()[0];
track.applyConstraints(constraints)
.then(() => {
// Do something with the track such as using the Image Capture API.
})
.catch(e => {
// The constraints could not be satisfied by the available devices.
});
});
更多不同的约束条件:
//使用1280x720的摄像头分辨率
{
audio: true,
video: { width: 1280, height: 720 }
}
//移动设备上面,优先使用前置摄像头
{ audio: true, video: { facingMode: "user" } }
//移动设备上面,强制使用后置摄像头
{ audio: true, video: { facingMode: { exact: "environment" } } }
如何播放MediaStream
可将 MediaStream 对象直接赋值给 HTMLMediaElement 接口的 srcObject 属性
video.srcObject = stream;
如何获取MediaStream
- 本地设备
通过调用 MediaDevices.getUserMedia() 来访问本地媒体,调用该方法后浏览器会提示用户给予使用媒体输入的许可, 媒体输入会产生一个 MediaStream, 里面包含了请求的媒体类型的轨道. 此流可以包含一个视频轨道( 来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等) 、一个音频轨道 (同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等), 也可能是其它轨道类型
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
/* 使用这个stream*/
video.srcObject = stream;
})
.catch(function(err) {
/* 处理error */
});
通过 MediaDevices.enumerateDevices() 我们可以得到一个本机可用的媒体输入和输出设备的列表, 例如麦克风, 摄像机, 耳机设备等
将该constraint值作为参数传入到MediaDevices.getUserMedia(constraints)中, 便可获得该设备的MediaStream。
cosnt constraints = { audio : audioDeviceInput }
//获取媒体设备
navigator.mediaDevices.enumerateDevices().then(res => {
console.log(res);
});
- 捕获屏幕
使用MediaDevices.getDisplayMedia()方法, 可以提示用户去选择和授权捕获展示的内容或部分内容(如一个窗口), 并将录制内容在一个 MediaStream 里
- HTMLCanvasElement.captureStream()
使用 HTMLCanvasElement.captureStream() 方法返回的 CanvasCaptureMediaStream 是一个实时捕获的 canvas 动画流
//frameRate设置双精准度浮点值为每个帧的捕获速率。
//如果未设置,则每次画布更改时都会捕获一个新帧。
//如果设置为0,则会捕获单个帧。
cosnt canvasStream = canvas.captureStream(frameRate);
video.srcObject = canvasSream;
RTCPeerConnection
从其他MediaStream中获取
可通过构造函数 MediaStream() 返回新建的空白的 MediaStream 实例
newStream = new MediaStream();
传入 MediaStream 对象,该 MediaStream 对象的数据轨会被自动添加到新建的流中. 且这些数据轨不会从原流中移除, 即变成了两条流共享的数据
newStream = new MediaStream(otherStream);
传入 MediaStreamTrack 对象的 Array 类型的成员,代表了每一个添加到流中的数据轨。
newStream = new MediaStream(tracks[]);
MediaStream.addTrack() 方法会给流添加一个新轨道。
MediaStream.clone() 方法复制一份副本 MediaStream 这个新的MediaStream对象有一个新的id。