js錄製系統/麥克風聲音(基於electron)

最近想用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);

--參數

  1. bufferSize:緩衝區大小 256 到 16384 之間的 2 的次冪, 爲 2565121024204840968192 或者 16384(爲0則由系統自動選取最優值)
  2. inputChannels :輸入node的聲道數(1-32)默認值是2
  3. outputChannels:輸出node的聲道數(1-32)默認值是2

--概念

  1. 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;

---------------

附:

  1. mateType https://www.iana.org/assignments/media-types/media-types.xhtml#video
  2. h5端調用有大神造好的輪子:https://www.cnblogs.com/xiaoqi/p/6993912.html  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章