【譯文】純HTML5捕獲音頻流和視頻流


原文地址:Capturing Audio & Video in HTML5

前言

長期以來音視頻捕獲一直是web開發的珍寶。多年來,我們不得不依賴瀏覽器插件( flash 或者 silverlight )來完成這個工作.

HTML5完成了救贖。可能並不明顯,但是HTML5的興起帶來了大量對這杯硬件的訪問。Geolocation(GPS)、Orientation API(加速度計)、WebGL(GPU)和Web Audio (音頻硬件)都是完美的例子。這些功能非常強大,暴露了位於系統底層硬件功能之上的高級JS api。

本篇教程介紹 navigator.mediaDevices.getUserMedia() ,這使得web應用能夠訪問用戶的攝像頭和麥克風.

探尋getUserMedia() 之路

如果你不清楚這段歷史,那我們這段探尋之旅將是一個非常有趣的故事。

在過去的幾年中, "Media Capture APIs" 的幾個變種都得到了發展。許多人意識到在web上需要訪問本設備,而這出事了每一個人共同制定一套新規范。事情變得很混亂(每個瀏覽器都有自己的規范),W3C最終決定成立一個工作組。他們唯一的目的就是去掉混亂。Device APIs Policy (DAP) 工作組已經把整合化和標准化大量提案作為他們的任務。

我將會嘗試總結下2011年發生的事情。。。

第一輪 HTML Media Capture

HTML Media Capture 是DAP首次在web上標准化如何訪問多媒體。它通過重載 <input type="file"> 標簽並新增accept參數的值來實現.

如果你想讓用戶使用網絡攝像頭來獲取一個快照,可以使用 capture=camera

<input type="file" accept="image/*;capture=camera">

錄制視頻和視頻也是類似的:

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">

看起來不錯吧?我特別喜歡它重用文件輸入。從語義上來說很好理解。這個api的不足之處是實時效果(比如:渲染實時網絡攝像頭數據到 <canvas> 並應用到 WebGL過濾器).HTML Media Capture僅允許你錄制和獲取快照(無法進行實時渲染).

第二輪 device 元素標簽

許多人認為 HTML Media Capture api過於局限,因此出現了支持任何類型(未來)設備的新規范。該設計不意外的使用了新標簽 ,該標簽是 getUserMedia() 的前身。

Opera是最早一批基於 <device> 標簽實現視頻捕獲的瀏覽器廠商之一。不久之后(准確來說是同一天),WhatWG (網頁超文本技術工作小組)決定取消 device 標簽轉向另一個方案:navigator.getUserMedia() 。一個星期后,Opera發布了支持getUserMedia()提案的新版本(這迭代速度剛剛的。。。)從那年以后,微軟也加入了新提案的大家庭:IE9 實驗室版支持新特性.

<device>就長這樣:

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
  function update(stream) {
    document.querySelector('video').src = stream.url;
  }
</script>

支持

不幸的是,瀏覽器正式版中一直沒有支持過 <device><device>有兩個好處:1. 語義化良好 2. 更容易去擴展而不僅僅支持音頻和視頻設備。

第三輪 WebRTC

<device>標簽最終被放棄了。。。

由於WebRTC(web實時通信)的努力使得找到合適api的速度加快了。這個規范由 W3C WebRTC小組監督。谷歌,Opera,火狐和一些公司已經實現。

getUserMedia()自從Chrome 21,Opera 18和Firefox 17開始支持。最初由Navigator.getUserMedia()提供支持的方法現在已經被廢除。你現在應該使用navigator.MediaDevices.getUserMedia(),這個方法普遍都支持。

有了getUserMedia,我們無需使用任何插件就可使用網絡攝像頭和麥克風輸入。訪問攝像頭只需要一個調用,而不是安裝插件。它直接被內置在瀏覽器中。有沒有點小激動!

功能檢測

對 navigator.mediaDevices.getUserMedia 是否支持進行簡單檢查:

function hasGetUserMedia() {
  return !!(navigator.mediaDevices &&
    navigator.mediaDevices.getUserMedia);
}

if (hasGetUserMedia()) {
  // Good to go!
} else {
  alert('getUserMedia() is not supported by your browser');
}

獲取一個輸入設備的訪問權限

為了使用攝像頭和麥克風,我們需要申請權限。getUserMedia()的參數是一個對象:對每一種你想訪問的每一種多媒體說明了具體和要求。比如,如果你想訪問攝像頭,參數應該為 {video: true}。同時使用攝像頭和麥克風,傳入 {video: true, audio: true}:

<video autoplay></video>

<script>
const constraints = {
  video: true
};

const video = document.querySelector('video');

navigator.mediaDevices.getUserMedia(constraints).
  then((stream) => {video.srcObject = stream});
</script>

ok.現在感覺怎么樣。。。媒體捕獲是HTML5協同工作的完美示例。它可以和其他HTML5小伙伴一起工作:

我們告訴 為autoplay,不讓它會在第一幀的時候卡住。你也可向vedit添加控件。

設置媒體限制(解析度,寬,高)

傳給getUserMedia()的參數可以用來對返回的媒體流做更多要求(或者限制)。比如,不僅僅指明你想訪問vedio(比如{vedio: true),你還可以添加額外的配置要求返回HD的流:

const hdConstraints = {
  video: {width: {min: 1280}, height: {min: 720}}
};

navigator.mediaDevices.getUserMedia(hdConstraints).
  then((stream) => {video.srcObject = stream});

...

const vgaConstraints = {
  video: {width: {exact: 640}, height: {exact: 480}}
};

navigator.mediaDevices.getUserMedia(vgaConstraints).
  then((stream) => {video.srcObject = stream});

如果當前被選擇的攝像頭不滿足解析度的要求,getUserMedia()將會被rejected一個OverconstrainedError,並且不會向用戶申請訪問攝像頭的提示。

選擇媒體源

navigator.mediaDevices.enumerateDevices() 方法提供了可用的輸入、輸出設備的信息,並可以選擇相機和麥克風(注:MediaStreamTrack.getSources() api已經被棄用)

這個例子允許用戶選擇音視頻源:

const videoElement = document.querySelector('video');
const audioSelect = document.querySelector('select#audioSource');
const videoSelect = document.querySelector('select#videoSource');

navigator.mediaDevices.enumerateDevices()
  .then(gotDevices).then(getStream).catch(handleError);

audioSelect.onchange = getStream;
videoSelect.onchange = getStream;

function gotDevices(deviceInfos) {
  for (let i = 0; i !== deviceInfos.length; ++i) {
    const deviceInfo = deviceInfos[i];
    const option = document.createElement('option');
    option.value = deviceInfo.deviceId;
    if (deviceInfo.kind === 'audioinput') {
      option.text = deviceInfo.label ||
        'microphone ' + (audioSelect.length + 1);
      audioSelect.appendChild(option);
    } else if (deviceInfo.kind === 'videoinput') {
      option.text = deviceInfo.label || 'camera ' +
        (videoSelect.length + 1);
      videoSelect.appendChild(option);
    } else {
      console.log('Found another kind of device: ', deviceInfo);
    }
  }
}

function getStream() {
  if (window.stream) {
    window.stream.getTracks().forEach(function(track) {
      track.stop();
    });
  }

  const constraints = {
    audio: {
      deviceId: {exact: audioSelect.value}
    },
    video: {
      deviceId: {exact: videoSelect.value}
    }
  };

  navigator.mediaDevices.getUserMedia(constraints).
    then(gotStream).catch(handleError);
}

function gotStream(stream) {
  window.stream = stream; // make stream available to console
  videoElement.srcObject = stream;
}

function handleError(error) {
  console.error('Error: ', error);

訪問Sam Dutton's great demo,這個例子顯示了如何讓用戶選擇媒體源。

安全

getUserMedia() 只能在HTTPS URL,localhost 或者 file://URL下使用,其他情況會被rejected掉。getUserMedia()也不能在跨域的iframe中使用。

所有的瀏覽器在調用getUserMedia()是都會彈出一個信息框,讓用戶可以選擇授權或者拒絕其攝像頭或者麥克風的使用權限。下圖是chrome的權限彈框:

這個授權是永久的。也就是說,用戶不用每次去授權(決絕/允許)。如果用戶后來改變了主意,他們可以瀏覽器設置中心針對每一個域進行設置。

貼士:攝像頭在使用的情況下,MediaStreamTrack處於激活, 這會占用資源,並且打開攝像頭(保持攝像頭燈打開),當你不再使用track的時候,確保調用 track.stop() 來使攝像頭關閉。

截屏

api可以調用ctx.drawImage(video, 0, 0) 方法來繪制 的幀到 上。當然,我們通過getUserMedia()獲得視頻輸入,使用實時視頻創建圖片phone booth引用很簡單。

<video autoplay></video>
<img src="">
<canvas style="display:none;"></canvas>

<script>
const captureVideoButton =
  document.querySelector('#screenshot .capture-button');
const screenshotButton = document.querySelector('#screenshot-button');
const img = document.querySelector('#screenshot img');
const video = document.querySelector('#screenshot video');

const canvas = document.createElement('canvas');

captureVideoButton.onclick = function() {
  navigator.mediaDevices.getUserMedia(constraints).
    then(handleSuccess).catch(handleError);
};

screenshotButton.onclick = video.onclick = function() {
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  canvas.getContext('2d').drawImage(video, 0, 0);
  // Other browsers will fall back to image/png
  img.src = canvas.toDataURL('image/webp');
};

function handleSuccess(stream) {
  screenshotButton.disabled = false;
  video.srcObject = stream;
}
</script>

應用效果

css濾鏡

使用css濾鏡,我們可以在 上捕獲的使用一些css效果.

<video autoplay></video>
<p><button class="capture-button">Capture video</button>
<p><button id="cssfilters-apply">Apply CSS filter</button></p>

<script>
const captureVideoButton =
  document.querySelector('#cssfilters .capture-button');
const cssFiltersButton =
  document.querySelector('#cssfilters-apply');
const video =
  document.querySelector('#cssfilters video');

let filterIndex = 0;
const filters = [
  'grayscale',
  'sepia',
  'blur',
  'brightness',
  'contrast',
  'hue-rotate',
  'hue-rotate2',
  'hue-rotate3',
  'saturate',
  'invert',
  ''
];

captureVideoButton.onclick = function() {
  navigator.mediaDevices.getUserMedia(constraints).
    then(handleSuccess).catch(handleError);
};

cssFiltersButton.onclick = video.onclick = function() {
  video.className = filters[filterIndex++ % filters.length];
};

function handleSuccess(stream) {
  video.srcObject = stream;
}
</script>

WebGL紋理

視頻捕獲一個神奇的應用就是作為一個WegGL紋理實時渲染。因為我對WebGL一無所知(除了它很牛逼),所以我建議你們看一個教程示例。它講了如何在WebGL里使用getUserMedia() 和Three.js去渲染實時vedio。

對web audio api使用getUserMedia

我的夢想之一就是在瀏覽器中構建autotune(不知道啥意思),而不是開放的web技術。

chrome支持從getUserMedia()到web audio api的實時麥克風輸入,以實現實時效果。

window.AudioContext = window.AudioContext ||
                      window.webkitAudioContext;

const context = new AudioContext();

navigator.mediaDevices.getUserMedia({audio: true}).
  then((stream) => {
    const microphone = context.createMediaStreamSource(stream);
    const filter = context.createBiquadFilter();
    // microphone -> filter -> destination
    microphone.connect(filter);
    filter.connect(context.destination);
});

總結

從發展歷程上來看,在web上訪問設備一直是個棘手的難題。做了許多嘗試,只有少部分獲得了成功。大多數早起的想法從未得到廣泛的采用,也沒有在特定環境之外被接受。主要的問題可能是web的安全模型與原生系統有很大的不同。特別是,您可能並不希望每個網站都可以隨機訪問您的攝像頭和麥克風。總之很難找到權衡之際。

在移動設備日益普及的功能驅動下,web開始提供更豐富的功能。我們現在可以利用很多api去拍照,控制攝像頭設置,錄制音頻和視頻,並訪問其他類型的傳感器數據,比如位置,運動和設備方向。通過傳感器框架將他們串在一起,並與api一起配合使用,使得we應用能訪問usb並與藍牙進行交互。

getUserMedia()只是與硬件交互的第一波熱潮。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM