談談MediaStream

MediaStream 是連接 WebRTC API 和底層物理流的中間層,webRTC將音視頻經過Vocie / Video engine進行處理后,再通過MediaStream API給暴露給上層使用。


image.png

概念

  • MediaStreamTrack
  • MediaStream
  • source
  • sink
  • MediaTrackConstraints

1. MediaStreamTrack

MediaStreamTrack是WebRTC中的基本媒體單位,一個MediaStreamTrack包含一種媒體源(媒體設備或錄制內容)返回的單一類型的媒體(如音頻,視頻)。單個軌道可包含多個頻道,如立體聲源盡管由多個音頻軌道構成,但也可以看作是一個軌道。
WebRTC并不能直接訪問或者控制源,對源的一切控制都可以通過軌道控制MediaTrackConstraints進行實施。

MediaStreamTrack MDN文檔
MediaTrackConstraints MDN文檔

2. MediaStream

MediaStream是MediaStreamTrack的合集,可以包含 >=0 個 MediaStreamTrack。MediaStream能夠確保它所包含的所有軌道都是是同時播放的,以及軌道的單一性。

MediaStream MDN文檔

3. 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資源等靜態(tài)資源以及麥克風采集的音頻,攝像頭采集的視頻等動態(tài)資源。而sink則負責消費source生產媒體資源,也就是通過<img>,<video>,<audio>等媒體標簽進行展示,或者是通過RTCPeerConnection將source通過網絡傳遞到遠端。RTCPeerConnection可同時扮演source與sink的角色,作為sink,可以將獲取的source降低碼率,縮放,調整幀率等,然后傳遞到遠端,作為source,將獲取的遠端碼流傳遞到本地渲染。

source 與sink構成一個MediaTrack,多個MeidaTrack構成MediaStram。

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

如何播放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()我們可以得到一個本機可用的媒體輸入和輸出設備的列表,例如麥克風,攝像機,耳機設備等。

//獲取媒體設備
navigator.mediaDevices.enumerateDevices().then(res => {
    console.log(res);
});
image.png

列表中的每個媒體輸入都可作為MediaTrackConstraints中對應類型的值,如一個音頻設備輸入audioDeviceInput可設置為MediaTrackConstraints中audio屬性的值

cosnt constraints = { audio : audioDeviceInput }

將該constraint值作為參數傳入到MediaDevices.getUserMedia(constraints)中,便可獲得該設備的MediaStream。

MediaDevices.enumerateDevices() MDN文檔
MediaDevices.getUserMedia() MDN文檔

  1. 捕獲屏幕

使用MediaDevices.getDisplayMedia()方法,可以提示用戶去選擇和授權捕獲展示的內容或部分內容(如一個窗口),并將錄制內容在一個MediaStream 里。

image.png

MediaDevices.getDisplayMedia() MDN文檔

  1. HTMLCanvasElement.captureStream()

使用HTMLCanvasElement.captureStream() 方法返回的 CanvasCaptureMediaStream 是一個實時捕獲的canvas動畫流。

//frameRate設置雙精準度浮點值為每個幀的捕獲速率。
//如果未設置,則每次畫布更改時都會捕獲一個新幀。
//如果設置為0,則會捕獲單個幀。
cosnt canvasStream = canvas.captureStream(frameRate);
video.srcObject = canvasSream;

HTMLCanvasElement.captureStream() MDN文檔
CanvasCaptureMediaStream MDN文檔

  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。

實現一個簡易的錄屏工具

  • 獲取捕獲屏幕的MeidaStream
const screenStream = await navigator.mediaDevices.getDisplayMedia();
  • 獲取本地音視頻數據
const cameraStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
const audioStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
  • 將屏幕畫面與攝像頭畫面繪制canvas中
function createStreamVideo(stream) {
    const video = document.createElement("video");
    video.srcObject = stream;
    video.autoplay = true;

    return video;
}

const cameraVideo = createStreamVideo(cameraStream);
const screenVideo = createStreamVideo(screenStream);
animationFrameHandler() {
      if (screenVideo) {
          canvas.drawImage(screenVideo, 0, 0, canvasWidth, canvasHeight);
      }

      if (cameraVideo) {
          canvas.drawImage(
              cameraVideo,
              canvasWidth - CAMERA_VIDEO_WIDTH,
              canvasHeight - CAMERA_VIDEO_HEIGHT,
              CAMERA_VIDEO_WIDTH,
              CAMERA_VIDEO_HEIGHT
          )
      }

      requestAnimationFrame(animationFrameHandler.bind(this));
  }
  • 獲取canvas的MediaStream
const recorderVideoStream = await canvas.captureStream();
  • 合并本地的音頻軌道和canvasStream的視頻軌道,獲得最終畫面的MediaStream
const stream = new MediaStream();
audioStream.getAudioTracks().forEach(track => stream.addTrack(track));
recorderVideoStream.getVideoTracks().forEach(track => stream.addTrack(track));

video.srcObject = stream;
  • 通過MediaRecorder對畫面進行錄制
const recorder = new MediaRecorder(stream, { mineType: "video/webm;codecs=h264" });
recorder.ondataavailable = e => {
    recorderVideo.src = URL.createObjectURL(e.data);
};

//開始錄制
recorder.start();

//停止錄制
recorder.stop();

MediaRecorder MDN文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容