最近想用electron寫一個和音視頻相關的軟件,儘管在寫之前都想好了要用哪些技術,但依然寫得很艱難,今天對相關知識的個人理解做個記錄和整理。
時隔半年,最近發現網上有大神造好的輪子,而且功能強大:https://www.cnblogs.com/xiaoqi/p/6993912.html
先看幾個瀏覽器api
1、AudioContext構造器
- AudioContext接口表示由音頻模塊連接而成的音頻處理上下文;它可以控制它所包含的節點的創建,以及音頻處理、解碼操作的執行。做任何事情之前都要先創建AudioContext對象,因爲一切都發生在這個環境之中。 more》
- 通過var audioCtx = new AudioContext()創建;
2、ScriptProcessorNode
方法
- ScriptProcessorNode是一個音頻節點(AudioNode),js可以通過它直接處理音頻
- 通過audioCtx.createScriptProcessor方法創建
- AudioContext.createScriptProcessor(bufferSize,inputChannels ,outputChannels);
--參數
- bufferSize:緩衝區大小
256
到16384
之間的 2 的次冪, 爲256
,512
,1024
,2048
,4096
,8192
或者16384(爲0則由系統自動選取最優值)
- inputChannels :輸入node的聲道數(1-32)默認值是2
- outputChannels:輸出node的聲道數(1-32)默認值是2
--概念
AudioNode
接口是一個處理音頻的通用模塊, 比如一個音頻源一個AudioNode
既有輸入也有輸出。輸入與輸出都有一定數量的通道。只有一個輸出而沒有輸入的AudioNode
叫做音頻源。more》
3、createMediaStreamSource方法
-
createMediaStreamSource用於通過音頻上下文創建一個可以被播放和處理的媒體流資源
-
它需要傳入一個流對象(可以通過
navigator.getUserMedia獲得
)
好了,開幹
獲取系統/麥克風音頻流的buffer:
// config.resourceType :媒體類型(可傳'system'|'device'指定獲取系統/麥克風聲音)
// config.bufferHanduler :buffer處理回調函數
// config.getMediaSuccessCallback :獲取媒體設備(系統聲道或這麥克風)成功後的回調函數
// config.errorHanduler :異常處理函數
class Recorder
{
constructor (desktopCapturer,config){
this.audioContext = null;
this.desktopCapturer = desktopCapturer; // electron的desktopCapturer對象
this.config = config;
}
start() {
if(this.audioContext && this.audioContext.state == 'running'){
this.stop();
}
if(!(navigator.getUserMedia && AudioContext)){
return false;
}
if(!this.AudioContext || this.audioContext.state == 'closed'){
this.audioContext = new AudioContext();
// audioContext.onstatechange = function(e){
// console.log(e);
// }
let audioNode = this.audioContext.createScriptProcessor(0, 1, 1);
let _self = this;
let getMediaSuccess = function(stream){
console.log('success get ===========');
let mediaSource = _self.audioContext.createMediaStreamSource(stream);
mediaSource.connect(audioNode);
audioNode.connect(_self.audioContext.destination);
audioNode.onaudioprocess = (e) => {
// 好了,這裏就獲取到了音頻流的buffer,你可以爲所欲爲了,嘿嘿。。
// 我這裏發給另個一個線程把buffer轉成pcm編碼
_self.config.bufferHanduler && _self.config.bufferHanduler(e.inputBuffer.getChannelData(0));
}
// 獲取媒體成功,執行回調
_self.config.getMediaSuccessCallback && _self.config.getMediaSuccessCallback();
}
// 獲取音視頻媒體
if(this.config.resourceType == 'system'){
// 系統音頻流
this.desktopCapturer.getSources(
{types: ['screen']}
).then(async sources => {
for (const source of sources) {
console.log(source.name);
if (source.name === "Entire screen" || source.name === "Entire Screen") {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
mandatory: {
// cursor:"never",
chromeMediaSource: 'desktop'
}
},
audio: {
mandatory: {
chromeMediaSource: 'desktop',
}
}
});
getMediaSuccess(stream);
} catch (err) {
this.config.errorHanduler && this.config.errorHanduler(err);
}
}
}
});
}else if(this.config.resourceType == 'device'){
// 麥克風音頻流
navigator.mediaDevices.getUserMedia({ video: false, audio: true }).then(function(stream){
if(!stream){
this.config.errorHanduler && this.config.errorHanduler('讀取設備失敗,請確認你的設備是否已經正確連接好麥克風設備!');
return;
}
getMediaSuccess(stream);
}).catch(function(err){
this.config.errorHanduler && this.config.errorHanduler(err);
});
}else{
this.config.errorHanduler && this.config.errorHanduler('不支持此類型!');
}
}
}
stop(){
try{
this.audioContext.close();
}catch (e){
console.log(e);
}
}
restart(){
if(this.audioContext && this.audioContext.state == 'running'){
this.stop();
}
this.start();
}
}
export default Recorder;
---------------
附:
- mateType https://www.iana.org/assignments/media-types/media-types.xhtml#video
- h5端調用有大神造好的輪子:https://www.cnblogs.com/xiaoqi/p/6993912.html