HTML5呼叫攝像頭+視訊特效+錄製視訊+錄音+截圖+變聲+濾波+音訊視覺化
1.寫在前面
根據最近的學習,寫了一個demo, 可以通過navigator.mediaDevices.getUserMedia()方法呼叫電腦攝像頭,並實現了錄製音訊,錄製視訊,對攝像頭的內容進行截圖下載;通過AudioContext使用web audio api處理音訊資訊,實現濾波以及簡單變聲處理。錄製使用的api是MediaRecorder。整個demo沒有使用其他的庫,全部是原生api. 本專案程式碼我會上傳。
2.實現過程
(1)呼叫攝像頭 pc端大部分瀏覽器支援getUserMedia()方法,安卓端除了chrome for android支援的很少;同時,要注意,在本地呼叫這個方法被禁止,只能在線上網頁使用,我的測試使用了nodejs方法,寫一個簡單的後臺,構建一個http伺服器,localhost方式訪問。使用navigator.mediaDevices.getUserMedia()獲取視訊,返回值是一個promise型別;呼叫程式碼如下:
var video = document.querySelector('video');
var option = {audio:false,video:{width:640,height:480}};
var media = navigator.mediaDevices.getUserMedia(option);
media.then(show).catch((error)=>{console.log(error)});
functionshow(mediaStream){
video.src = window.URL.createObjectURL(mediaStream) ;
//將meida流轉為urlvideo.onloadedmetadata = function(e) {
video.play();
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
這段程式碼是呼叫核心,會請求使用者是否開啟攝像頭。方法引數是設定,具體參見MDN網站介面指導。其中,URL.createObjectURL(blob)這個方法會把blob格式資料轉為url,然後通過提供給audio,video等標籤就可以顯示,這個方法在我們的應用中很常用。
(2)視訊特效
實現了簡單的特效,grayscale/sepia/blur 分別是灰色畫面/黃色畫面特效/模糊效果等等,核心是使用css的filter去完成,<filter屬性不止可以使用在圖片上,也可以用在video標籤上!>
css:
video{
background: rgba(255,255,255,0.5);
}
.grayscale{
filter: grayscale(1);
}
.sepia{
filter:sepia(1);
}
.blur{
filter: blur(3px);
}
//js:
video.onclick = ()=>{
count++;
switch(count){
case 1:video.className = 'grayscale';break;
case 2:video.className = 'sepia';break;
case 3:video.className = 'blur';break;
case 4:video.className = '';count=0;break;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
然後設定每次點選讓video具有上面其中一個className,就可以具有不同特效了,很簡單。同時要明白這只是css特效,視訊本身並沒有改變。截圖等得到的畫面不會有這些css特效,如果需要視訊本身就有特效,可以被截圖捕捉,可以使用canvas播放視訊並處理:(我的簡單嘗試)
// var canvas = document.querySelector('canvas');
// var ctx = canvas.getContext('2d');
// setInterval(function(){
// ctx.drawImage(video,0,0,640,480);
// var imgdata = ctx.getImageData(0,0,640,480);
// //反相處理
// for(var i = 0; i < imgdata.height; ++i){
// for(var j = 0; j < imgdata.width; ++j){
// var x = i*4*imgdata.width + 4*j,
// //每個畫素需要佔用4位資料,分別是r,g,b,alpha透明通道
// r = imgdata.data[x],
// g = imgdata.data[x+1],
// b = imgdata.data[x+2];
// imgdata.data[x+3] = 150;
// //圖片反相:
// imgdata.data[x] = 255-r;
// imgdata.data[x+1] = 255-g;
// imgdata.data[x+2] = 255-b;
// }
// }
// ctx.putImageData(imgdata,0,0);
// },100);
// }
//以上程式碼實現了反轉效果,視訊變成負片樣式,顯示在canvas上,但是canvas顯示視訊自然效能比<video>差很多。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
(3)截圖+下載
截圖的思路是構建一個canvas標籤,使用drawImage()把視訊一幀繪製在canvas上,再使用canvas.toBlob()方法得到blob資料,這時就可以顯示在頁面或者儲存了(這裡是用了URL.createObjectURL(blob)),下載的思路是構建一個a標籤,href賦值上面的url,download設定名稱,然後a.click()就ok了。類似於<a href="xxx" download="aaa">點我下載</a>
function shoot(){
var canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = 480;
var ctx = canvas.getContext('2d');
ctx.drawImage(video,0,0);
canvas.toBlob((myblob)=>{
download(myblob);
});
}
functiondownload(blob){
vara = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = Math.random().toString(36).substr(2,14);
a.click();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
Math.random().toString(36).substr(2,14) 這句程式碼可以得到14位隨機字串,這樣就可以讓檔名是一個隨機字串而不會重複。 (4)使用web audio api—AudioContext (音訊視覺化+濾波+變聲) 這個api很強大,使用也是相當複雜,方法極多。建議先去查一下詳細介紹,這裡我只談關鍵點。 這裡是音訊視覺化部分,可以實時隨著音調變化數字大小以及黃色條帶的長度,實現一個簡單的音訊視覺化,如果需要做華麗的視覺化也就不難了。
html:
<p id="volume"></p>
<div class="box"></div>
<br>
高通濾波:<input type="range" min='20' max='15000' step='40' value='20' id="high">
<p id='level'>0</p>
<p>只有高於此頻率的才能通過,濾掉低頻率內容</p>
js:
var audio = document.querySelector('audio');
var audioctx = new (window.AudioContext || window.webkitAudioContext);
var filter_high = audioctx.createBiquadFilter();
var analyser = audioctx.createAnalyser();
var dataArray = new Uint8Array(analyser.frequencyBinCount);
var media1 = navigator.mediaDevices.getUserMedia({audio:true});
media1.then(getdata).catch((error)=>{alert(error)});
function getdata(mediaStream){
var mic = audioctx.createMediaStreamSource(mediaStream);
audio.src = window.URL.createObjectURL(mediaStream);
mic.connect(analyser);
filter_high.type = 'highpass';
filter_high.frequency.value = 20;
analyser.connect(filter_high);
//filter_high.connect(audioctx.destination);
//analyser.connect(audioctx.destination);
}
elVolume = document.getElementById('volume');
var box = document.querySelector('.box');
function draw(){
var x = Math.floor(getByteFrequencyDataAverage());
elVolume.innerHTML = x;
box.style.width = 2+x*3+'px';
};
setInterval(draw,100);
function getByteFrequencyDataAverage(){
analyser.getByteFrequencyData(dataArray);
return dataArray.reduce(function(previous, current) {
return previous + current;
}) / analyser.frequencyBinCount;
};
//以下是簡單的高頻濾波部分
var high = document.querySelector('#high');
high.onchange = function(){
filter_high.frequency.value = high.value;
document.querySelector('#level').innerText = high.value;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
首先建立AudioContext,建立圖形化需要的Analyser,濾波需要的BiquadFilter; 通過audioctx.createMediaStreamSource(mediaStream) 把麥克風的聲音處理一下,connect連線到analyser,
filter_high.type = ‘highpass’; filter_high.frequency.value = 20;設定濾波器是高通濾波器,界限為20HZ,高於20HZ才能輸出。
var dataArray = new Uint8Array(analyser.frequencyBinCount);這句是建立一個無符號陣列,用於analyser.getByteFrequencyData(dataArray);方法獲取analyser得到的音調資訊,並存在dataArray裡面,通過求一個實際段內音調的均值得到此瞬間的音調是多少,然後draw()執行,改變
<p id="volume"></p>
<div class="box"></div>
的表現,實現音訊視覺化,具體參見MDN;
那麼變聲怎麼做呢,使用ScriptProcessor這個node,就可以對音訊內容做處理然後再輸出,也就能讓我們去做變聲處理了。
//通過ScriptProcessorNode處理音訊
//變聲音 播放速度減小 聲音變粗 速度增大 聲音變細
//反應在程式碼就是取樣點減少。。
var style = document.querySelector('#audio_style');
style.onchange = function(){
audio_style = parseInt(style.value);
}
var audio_style = 0;
var scriptNode = audioctx.createScriptProcessor(4096, 1, 1);
scriptNode.onaudioprocess = function(audioProcessingEvent) {
var inputBuffer = audioProcessingEvent.inputBuffer;
var outputBuffer = audioProcessingEvent.outputBuffer;
for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
var inputData = inputBuffer.getChannelData(channel);
var outputData = outputBuffer.getChannelData(channel);
// Loop through the 4096 samples
for (var sample = 0; sample < inputBuffer.length; sample++) {
// outputData[sample] = inputData[sample]*1;
//這裡做處理即可
switch(audio_style){
case 0:outputData[sample] = inputData[sample];break;
case 1:if(sample%2==0){
outputData[sample] = inputData[sample/2];}
else{
outputData[sample] = inputData[(sample-1)/2];
};break;
}
}
}
}
filter_high.connect(scriptNode);
scriptNode.connect(audioctx.destination);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
這裡的style就是html上的一個select,我設定了兩個value,一個正常一個是聲音加粗變聲特效,實現方式就是
if(sample%2==0){outputData[sample]=inputData[sample/2];}
else{outputData[sample] = inputData[(sample-1)/2];
只把每個取樣區間的前一半資料輸出,這樣就可以基本實現取樣速度減小一半,播放起來的效果是就是聲音很粗,很低沉。我只簡單做了一個聲音變粗的處理,還沒有想出複雜的變聲技巧,但是肯定是在上面這個地方做處理的,按照一定的演算法修改輸出buffer資料,知道了核心處理方法剩下的就是演算法層面了。
filter_high.connect(scriptNode);
scriptNode.connect(audioctx.destination);
- 1
- 2
這兩句程式碼意思是把之前那個濾波器連線到處理模組再把處理模組連線到輸出裝置上。我們就可以聽到處理後的聲音了。 (5)錄製視訊+錄製音訊 錄製視訊與音訊的核心是MediaRecorder這樣一個api,可以把stream流轉為blob,然後根據我們的設定儲存,使用的方法有start,stop等方法,ondataavailable等事件。先上程式碼: 錄製音訊:
<button id="record_audio">錄音頻</button>
<button id="stop_audio">stop</button>
<audio controls="controls" id="record_play"></audio>
js:
var record_audio = document.querySelector('#record_audio');
var stop_audio = document.querySelector('#stop_audio');
var record_play = document.querySelector('#record_play');
var audio_stream;
var media1 = navigator.mediaDevices.getUserMedia({audio:true});
media1.then((stream)=>{
audio_stream = stream;
}).catch((error)=>{alert(error)});
record_audio.onclick = function(){
record_audio.disabled = 'true';
varmediaRecorder = newMediaRecorder(audio_stream);
mediaRecorder.ondataavailable = function(e) {
// blob_audio = newBlob([e.data], {type:e.data.type});
blob_audio = newBlob([e.data], {type:'audio/mp3'});
//可以存為mp3格式但是本地無法播放,只有網頁可以播放
}
mediaRecorder.start();
mediaRecorder.onstop = function(){
record_play.src = URL.createObjectURL(blob_audio);
record_play.play();
//download(blob_audio);
}
stop_audio.onclick = function(){
record_audio.disabled = '';
mediaRecorder.stop();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
錄製過程就是new MediaRecorder(audio_stream)建立一個mediaRecorder , ondataavailable方法下,將e.data資料轉為blob格式:new Blob([e.data], {type:’audio/mp3’}); 其實這裡可以使用原本格式,e,data.type,看了一下返回的是video/webm, 很迷,改成mp3之後,在網頁端依舊可以播放,但是儲存到本地就無法播放(未解決),然後的流程和上面知識點差不多。 錄製視訊:視訊錄製與音訊錄製差別不大,
<button id="record">錄視訊</button>
<button id="stop">stop</button>
js:
var record = document.querySelector('#record');
var stop_record = document.querySelector('#stop');
var video2 = document.querySelector('#video2');
var video_stream;
//var media = navigator.mediaDevices.getUserMedia(option);
//這個media就是上面那個
media.then((stream)=>{
video_stream = stream;
}).catch((error)=>{alert(error)});
record.onclick = function(){
record.disabled = 'true';
varmediaRecorder = newMediaRecorder(video_stream);
mediaRecorder.ondataavailable = function(e) {
myblob = newBlob([e.data], {type:e.data.type});
}
mediaRecorder.start();
mediaRecorder.onstop = function(){
//download(myblob);
//播放器不能播放webm格式視訊,只能網頁播放
//可能需要使用庫轉碼
video2.src = URL.createObjectURL(myblob);
video2.play();
}
stop_record.onclick = function(){
record.disabled = '';
mediaRecorder.stop();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
這裡的視訊就是video/webm格式,還不能轉為其他格式,否則無法播放,webm格式我的本地播放器不支援,所以就沒有下載,直接在網頁端播放了,網上查到貌似不能音訊視訊一起儲存;如果需要其他格式的視訊,可以使用庫轉碼,或者參考網上js轉碼過程。
3.總結
本次demo我研究了一兩天,也邁過了很多坑,找到了合適的實現方式,目前pc端支援情況還不錯,之前找這方面的東西,找了很久都沒有找到很全面的,所以我自己實現了一遍,雖然很多地方還沒有完美解決,但是核心的問題都一一解決了。繼續努力,希望幫到大家。