談?wù)凪ediaStream

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


image.png

概念

  • MediaStreamTrack
  • MediaStream
  • source
  • sink
  • MediaTrackConstraints

1. MediaStreamTrack

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

MediaStreamTrack MDN文檔
MediaTrackConstraints MDN文檔

2. MediaStream

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

MediaStream MDN文檔

3. source 與 sink

再M(fèi)ediaTrack的源碼中,MediaTrack都是由對(duì)應(yīng)的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負(fù)責(zé)生產(chǎn)媒體資源,包括多媒體文件,web資源等靜態(tài)資源以及麥克風(fēng)采集的音頻,攝像頭采集的視頻等動(dòng)態(tài)資源。而sink則負(fù)責(zé)消費(fèi)source生產(chǎn)媒體資源,也就是通過(guò)<img>,<video>,<audio>等媒體標(biāo)簽進(jìn)行展示,或者是通過(guò)RTCPeerConnection將source通過(guò)網(wǎng)絡(luò)傳遞到遠(yuǎn)端。RTCPeerConnection可同時(shí)扮演source與sink的角色,作為sink,可以將獲取的source降低碼率,縮放,調(diào)整幀率等,然后傳遞到遠(yuǎn)端,作為source,將獲取的遠(yuǎn)端碼流傳遞到本地渲染。

source 與sink構(gòu)成一個(gè)MediaTrack,多個(gè)MeidaTrack構(gòu)成MediaStram。

4. MediaTrackConstraints

MediaTrackConstraints描述MediaTrack的功能以及每個(gè)功能可以采用的一個(gè)或多個(gè)值,從而達(dá)到選擇和控制源的目的。 MediaTrackConstraints 可作為參數(shù)傳遞給applyConstraints()以達(dá)到控制軌道屬性的目的,同時(shí)可以通過(guò)調(diào)getConstraints()用來(lái)查看最近應(yīng)用自定義約束。

const constraints = {
  width: {min: 640, ideal: 1280},
  height: {min: 480, ideal: 720},
  advanced: [
    {width: 1920, height: 1280},
    {aspectRatio: 1.333}
  ]
};

//{ video: true }也是一個(gè)MediaTrackConstraints對(duì)象,用于指定請(qǐng)求的媒體類型和相對(duì)應(yīng)的參數(shù)。
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對(duì)象直接賦值給HTMLMediaElement 接口的 srcObject屬性。

video.srcObject = stream;

srcObject MDN文檔

如何獲取MediaStream

  1. 本地設(shè)備

可通過(guò)調(diào)用MediaDevices.getUserMedia()來(lái)訪問(wèn)本地媒體,調(diào)用該方法后瀏覽器會(huì)提示用戶給予使用媒體輸入的許可,媒體輸入會(huì)產(chǎn)生一個(gè)MediaStream,里面包含了請(qǐng)求的媒體類型的軌道。此流可以包含一個(gè)視頻軌道(來(lái)自硬件或者虛擬視頻源,比如相機(jī)、視頻采集設(shè)備和屏幕共享服務(wù)等等)、一個(gè)音頻軌道(同樣來(lái)自硬件或虛擬音頻源,比如麥克風(fēng)、A/D轉(zhuǎn)換器等等),也可能是其它軌道類型。

navigator.mediaDevices.getUserMedia(constraints)
  .then(function(stream) {
    /* 使用這個(gè)stream*/
    video.srcObject = stream;
  })
  .catch(function(err) {
    /* 處理error */
  });

通過(guò)MediaDevices.enumerateDevices()我們可以得到一個(gè)本機(jī)可用的媒體輸入和輸出設(shè)備的列表,例如麥克風(fēng),攝像機(jī),耳機(jī)設(shè)備等。

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

列表中的每個(gè)媒體輸入都可作為MediaTrackConstraints中對(duì)應(yīng)類型的值,如一個(gè)音頻設(shè)備輸入audioDeviceInput可設(shè)置為MediaTrackConstraints中audio屬性的值

cosnt constraints = { audio : audioDeviceInput }

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

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

  1. 捕獲屏幕

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

image.png

MediaDevices.getDisplayMedia() MDN文檔

  1. HTMLCanvasElement.captureStream()

使用HTMLCanvasElement.captureStream() 方法返回的 CanvasCaptureMediaStream 是一個(gè)實(shí)時(shí)捕獲的canvas動(dòng)畫流。

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

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

  1. RTCPeerConnection

  2. 從其他MediaStream中獲取

可通過(guò)構(gòu)造函數(shù)MediaStream() 返回新建的空白的 MediaStream 實(shí)例

newStream = new MediaStream();
  • 傳入 MediaStream 對(duì)象,該 MediaStream 對(duì)象的數(shù)據(jù)軌會(huì)被自動(dòng)添加到新建的流中。且這些數(shù)據(jù)軌不會(huì)從原流中移除,即變成了兩條流共享的數(shù)據(jù)。
newStream = new MediaStream(otherStream);
  • 傳入 MediaStreamTrack 對(duì)象的 Array 類型的成員,代表了每一個(gè)添加到流中的數(shù)據(jù)軌。
newStream = new MediaStream(tracks[]);
  • MediaStream.addTrack()方法會(huì)給流添加一個(gè)新軌道。
  • MediaStream.clone()方法復(fù)制一份副本 MediaStream。這個(gè)新的MediaStream對(duì)象有一個(gè)新的id

實(shí)現(xiàn)一個(gè)簡(jiǎn)易的錄屏工具

  • 獲取捕獲屏幕的MeidaStream
const screenStream = await navigator.mediaDevices.getDisplayMedia();
  • 獲取本地音視頻數(shù)據(jù)
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;
  • 通過(guò)MediaRecorder對(duì)畫面進(jìn)行錄制
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文檔

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容