← 返回首页

SIGNAL · POST

MediaStream 简介

约 10 分钟阅读 bajiu 前端

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

MediaStream

一个 MediaStream 对象包含零个或更多的 MediaStreamTrack 对象, 代表着各种的声轨和视频轨. 每一个 MediaStreamTrack 可能有一个或更多的通道. 这个通道代表着媒体流的最小单元, 比如一个音频信号对应着一个对应的扬声器, 像是在立体声音轨中的左通道或右通道。MediaTrack由 sourcesink 组成. WebRTC并不能直接访问或者控制源, 对源的一切控制都可以通过轨道控制 MediaTrackConstraints 进行实施

MediaStreamTrack

MediaStreamTrackWebRTC 中的基本媒体单位, 一个 MediaStreamTrack 包含一种媒体源(媒体设备或录制内容)返回的单一类型的媒体(如音频,视频).单个轨道可包含多个通道, 如立体声源尽管由多个音频轨道构成, 但也可以看作是一个轨道

MediaStream

MediaStreamMediaStreamTrack 的合集, 可以包含 >=0 个 MediaStreamTrack . MediaStream 能够确保它所包含的所有轨道都是是同时播放的, 以及轨道的单一性

MediaStream MDN文档

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);
}

浏览器中存在从 sourcesink 的媒体管道, 其中 source 负责生产媒体资源, 包括多媒体文件, web 资源等静态资源以及麦克风采集的音频, 摄像头采集的视频等动态资源。而sink则负责消费source生产媒体资源, 也就是通过video 等媒体标签进行展示, 或者是通过 RTCPeerConnectionsource 通过网络传递到远端。 RTCPeerConnection 可同时扮演 sourcesink 的角色,作为 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;

srcObject MDN文档

如何获取MediaStream

  1. 本地设备

通过调用 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);
});
  1. 捕获屏幕

使用MediaDevices.getDisplayMedia()方法, 可以提示用户去选择和授权捕获展示的内容或部分内容(如一个窗口), 并将录制内容在一个 MediaStream

  1. HTMLCanvasElement.captureStream()

使用 HTMLCanvasElement.captureStream() 方法返回的 CanvasCaptureMediaStream 是一个实时捕获的 canvas 动画流

//frameRate设置双精准度浮点值为每个帧的捕获速率。
//如果未设置,则每次画布更改时都会捕获一个新帧。
//如果设置为0,则会捕获单个帧。
cosnt canvasStream = canvas.captureStream(frameRate);
video.srcObject = canvasSream;
  1. RTCPeerConnection

  2. 从其他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。