談起音視頻,前端能作些什麼

@(音視頻)[Audio|Video|MSE]javascript

音視頻隨着互聯網的發展,對音視頻的需求愈來愈多,然而音視頻無亂是播放仍是編解碼,封裝對性能要求都比較高,那現階段的前端再音視頻領域都能作些什麼呢。html


[TOC]前端

音頻或視頻的播放

html5 audio

提起音視頻的播放,我萌首先想到的是HTMLMediaElementvideo播放視頻,audio播放音頻。舉個栗子:html5

<audio controls autoplay loop="true" preload="auto" src="audio.mp3"></audio>
  • controls指定瀏覽器渲染成html5 audio.
  • autoplay屬性告訴瀏覽器,當加載完的時候,自動播放.
  • loop屬性循環播放.
  • preload當渲染到audio元素時,便加載音頻文件.
  • 移動端的瀏覽器並不支持autoplaypreload 屬性,即不會自動加載音頻文件,只有經過一些事件觸發,好比touchclick事件等觸發加載而後播放.
  • 媒體元素還有一些改變音量,某段音頻播放完成事件等,請閱讀HTMLMediaElement.
  • 固然若是你的網頁是跑在WebView中,可讓客戶端設置一些屬性實現預加載和自動播放。

AudioContext

雖然使用html5的audio能夠播放音頻,可是正如你看到存在不少問題,同時我萌不能對音頻的播放進行很好的控制,好比說從網絡中獲取到音頻二進制數據,有的時候我萌想順序播放多段音頻,對於使用audio元素也是力不從心,處理起來並不優雅。
舉個栗子:java

function queuePlayAudio(sounds) {
    let index = 0;
    function recursivePlay(sounds, index) {
        if(sounds.length == index) return;
        sounds[index].play();
        sounds[index].onended = recursivePlay.bind(this, sounds, ++index);
    }
}

監聽audio元素的 onended 事件,順序播放。c++

爲了更好的控制音頻播放,我萌須要AudioContext.git

AudioContext接口表示由音頻模塊鏈接而成的音頻處理圖,每一個模塊對應一個AudioNode。AudioContext能夠控制它所包含的節點的建立,以及音頻處理、解碼操做的執行。作任何事情以前都要先建立AudioContext對象,由於一切都發生在這個環境之中。

可能理解起來比較晦澀,簡單的來講,AudioContext 像是一個工廠,對於一個音頻的播放,從音源到聲音控制,到連接播放硬件的實現播放,都是由各個模塊負責處理,經過connect 實現流程的控制。
點擊查看github

如今我萌便能實現音頻的播放控制,好比從網絡中獲取。利用AJAX中獲取 arraybuffer類型數據,經過解碼,而後把音頻的二進制數據傳給AudioContext建立的BufferSourceNode,最後經過連接 destination 模塊實現音頻的播放。web

export default class PlaySoundWithAudioContext {
    constructor() {
            if(PlaySoundWithAudioContext.isSupportAudioContext()) {
                this.duration = 0;
                this.currentTime = 0;
                this.nextTime = 0;
                this.pending = [];
                this.mutex = false;
                this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
            }
        }
        static isSupportAudioContext() {
            return window.AudioContext || window.webkitAudioContext;
        }

       play(buffer) {
            var source = this.audioContext.createBufferSource(); 
            source.buffer = buffer;                  
            source.connect(this.audioContext.destination); 
            source.start(this.nextTime);
            this.nextTime += source.buffer.duration;
        }

    addChunks(buffer) {
        this.pending.push(buffer);
        let customer = () => {
            if(!this.pending.length) return;
            let buffer = this.pending.shift();
            this.audioContext.decodeAudioData(buffer, buffer => {
            this.play(buffer);
            console.log(buffer)
            if(this.pending.length) {
                customer()
            }
            }, (err) => {
                console.log('decode audio data error', err);
            });
        }
        if(!this.mutex) {
            this.mutex = true;
            customer()
        }
       
    }

    clearAll() {
        this.duration = 0;
        this.currentTime = 0;
        this.nextTime = 0;
    }
}

AJAX調用api

function xhr() {
    var XHR = new XMLHttpRequest();
   XHR.open('GET', '//example.com/audio.mp3');
  XHR.responseType = 'arraybuffer';
  XHR.onreadystatechange = function(e) {
      if(XHR.readyState == 4) {
         if(XHR.status == 200) {
       playSoundWithAudioContext.addChunks(XHR.response);
    }
      }
   }
  XHR.send();
}

使用Ajax播放對於小段的音頻文件還行,可是一大段音頻文件來講,等到下載完成才播放,不太現實,可否一邊下載一邊播放呢。這裏就要利用 fetch 實現加載stream流。

fetch(url).then((res) => {
    if(res.ok && (res.status >= 200 && res.status <= 299)) {
        readData(res.body.getReader())
    } else {
        that.postMessage({type: constants.LOAD_ERROR})
    }
})

function readData(reader) {
    reader.read().then((result) => {
        if(result.done) {
            return;
        }
        console.log(result);
        playSoundWithAudioContext.addChunks(result.value.buffer);
    })
}

簡單的來講,就是fetchresponse返回一個readableStream接口,經過從中讀取流,不斷的餵給audioContext 實現播放,測試發現移動端不能順利實現播放,pc端瀏覽器能夠。

PCM audio

實現audioContext播放時,我萌須要解碼,利用decodeAudioDataapi實現解碼,我萌都知道,通常音頻都要壓縮成mp3,aac這樣的編碼格式,我萌須要先解碼成PCM數據才能播放,那PCM 又是什麼呢?我萌都知道,聲音都是由物體振動產生,可是這樣的聲波沒法被計算機存儲計算,我萌須要使用某種方式去刻畫聲音,因而乎便有了PCM格式的數據,表示麥克風採集聲音的頻率,採集的位數以及聲道數,立體聲仍是單聲道。

Media Source Extensions

Media Source Extensions能夠動態的給AudioVideo建立stream流,實現播放,簡單的來講,能夠很好的播放進行控制,好比再播放的時候實現 seek 功能什麼的,也能夠在前端對某種格式進行轉換進行播放,並非支持全部的格式的。
點擊查看

經過將數據append進SourceBuffer中,MSE把這些數據存進緩衝區,解碼實現播放。這裏簡單的舉個使用MSE播放 audio的栗子:

export default class PlaySoundWithMSE{
    constructor(audio) {
        this.audio = audio;
        if(PlaySoundWithMSE.isSupportMSE()) {
            this.pendingBuffer = [];
            this._mediaSource = new MediaSource();
            this.audio.src = URL.createObjectURL(this._mediaSource);
            this._mediaSource.addEventListener('sourceopen', () => {
                this.sourcebuffer = this._mediaSource.addSourceBuffer('audio/mpeg');
                this.sourcebuffer.addEventListener('updateend', 
                this.handleSourceBufferUpdateEnd.bind(this));
            })
        }
    }

    addBuffer(buffer) {
        this.pendingBuffer.push(buffer);
    }

    handleSourceBufferUpdateEnd() {
        if(this.pendingBuffer.length) {
            this.sourcebuffer.appendBuffer(this.pendingBuffer.shift());
        } else {
            this._mediaSource.endOfStream();
        }
    }

    static isSupportMSE() {
        return !!window.MediaSource;
    }
}

HTML5 播放器

談起html5播放器,你可能知道bilibili的flv.js,它即是依賴Media Source Extensions將flv編碼格式的視頻轉包裝成mp4格式,而後實現播放。
點擊查看

從流程圖中能夠看到,IOController實現對視頻流的加載,這裏支持fetch的 stream能力,WebSocket等,將獲得的視頻流,這裏指的是flv格式的視頻流,將其轉封裝成MP4格式,最後將MP4格式的數據經過appendBuffer將數據餵給MSE,實現播放。

將來

上面談到的都是視頻的播放,你也看到,即便播放都存在不少限制,MSE的瀏覽器支持還很少,那在視頻的編碼解碼這些要求性能很高的領域,前端可否作一些事情呢?
前端性能不高有不少緣由,在瀏覽器這樣的沙盒環境下,同時js這種動態語言,性能不高,因此有大佬提出把c++編譯成js ,而後提升性能,或許你已經知道我要說的是什麼了,它就是ASM.js,它是js的一種嚴格子集。我萌能夠考慮將一些視頻編碼庫編譯成js去運行提升性能,其中就不得不提到的FFmpeg,能夠考慮到將其編譯成asm,而後對視頻進行編解碼。
點擊查看

寫在最後

我萌能夠看到,前端對音視頻的處理上因爲諸多緣由,可謂如履薄冰,可是在視頻播放上,隨着瀏覽器的支持,仍是能夠有所做爲的。

招納賢士

今日頭條長期大量招聘前端工程師,可選北京、深圳、上海、廈門等城市。歡迎投遞簡歷到 tcscyl@gmail.com / yanglei.yl@bytedance.com

相關文章
相關標籤/搜索