咱們想到是否能夠以發送文件的方式發送過去呢?咱們首先找到微信保存在本地的語音,在tencent/MicroMsg下面,咱們發現微信的語音文件是.silk後綴名的未知格式文件,而且手機上沒有自帶app能夠打開這種格式的語音。那麼若是咱們直接找到該文件發送給好友,微信是否能自動識別而後讓好友聽到呢?答案是不能夠的,當咱們將該語音以文件形式發過去的時候好友並不能播放,說明微信也是作過限制的。那麼有沒有辦法能夠將微信的語音分享出去讓別人聽到呢?答案固然是能夠的,這就是做者今天要分享的內容,如何打開微信保存在本地的語音文件。想要實現該功能就必須瞭解音視頻基礎,接下來做者來說解一下。java
音視頻基礎數組
音視頻文件如何生成的?
bash
如上圖,圖像的原始格式是YUV/RGB。因爲原始數據過大,須要對原始圖像進行壓縮成H-264/VP8,壓縮過程也叫作編碼。微信
同理音頻的過程也大體相似,音頻的原始格式是PCM,同時也須要進行壓縮成AAC/AMR。
app
編碼之後將音頻和視頻封裝成一個MP4/FLV文件,即咱們常見的音視頻文件。ide
音視頻文件時如何播放的?工具
播放其實就是和生成相反的步驟,入上圖可看到,首先要解封裝,而後音頻和視頻分別進行解壓縮,解碼成系統可以識別的格式,進而調用系統硬件進行工做。性能
其實安卓自己提供了錄製和播放PCM的操做,只是因爲MediaRecorder使用起來更簡單易懂,因此可能不是很瞭解。通過對音視頻編解碼的瞭解,那麼思路就有了,只要咱們能將微信的.silk文件裏的PCM採集出來,那是否是就能夠經過Android自帶的PCM播放器AudioTrack播放出來了?下面做者就帶領你們一塊兒學習一下AudioTrack播放PCM的byte數據。
學習
AudioTrack其實使用起來很是簡單,只須要4步就搞定了開發工具
步驟一 建立AudioTrack對象
int mBufferSizeInBytes = AudioTrack.getMinBufferSize(
sampleRate, // 採樣率,每秒採集數據次數
AudioFormat.CHANNEL_OUT_MONO, // 聲道
AudioFormat.ENCODING_PCM_16BIT); // 位數,一個數據16位
mAudioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, // 音樂流 (告訴系統將該數據當作音樂來播放)
sampleRate, // 採樣率 (每秒採樣數據的次數,次數越大聲音越真實,可是耗性能)
AudioFormat.CHANNEL_OUT_MONO, // 聲道 (雙聲道、單聲道等選擇好,如今是單聲道)
AudioFormat.ENCODING_PCM_16BIT, //採集的數據每一個數據所佔的位數
mBufferSizeInBytes, // 緩衝區大小,上面設置好的
AudioTrack.MODE_STREAM); //設置以流的形式進行播放步驟二 啓動播放器
mAudioTrack.play();
步驟三 開始播放(MODE_STREAM)
mAudioTrack.write(mOutputBuffer, 0, len);
步驟四 中止與釋放
mAudioTrack.stop();
mAudioTrack.release();
以上播放的準備已經作好了,只須要拿到PCM往上面的AudioTrack中傳入就行了,那麼今天重點來了,應該怎麼作才能將silk文件中的PCM採集出來呢?
如何從微信語音文件中獲取PCM數據
想獲取其中的數據就必須知道silk是什麼格式,其實Silk是Skype開發的開源的音頻壓縮格式和音頻編解碼器。它是爲在Skype中使用而開發的。實時帶寬6-40Kbps便可工做,即便丟包水平達到10%依然能夠穩定維持24KHz採樣的通話音質,這個是該技術行業很是領先的。
那麼Skype又是什麼產品呢,Skype是一款即時通信軟件,其具有IM所需的功能,好比視頻聊天、多人語音會議、多人聊天、傳送文件、文字聊天等功能,相似於微信QQ。
總結一下,silk就是就是一個C語言開發的開源的音頻編解碼庫。既然微信是使用的第三方的編碼方式,因此思路來了,咱們只須要讓開源的silk庫對微信語音文件進行解碼,再播放便可。
前面說過,silk是一個C語言的庫,若是想在Android中使用到C語言的庫,就必須進行NDK的開發了。
什麼是JNI和NDK?
JNI:
Java Native Interface,JDK提供的一套API,實現了Java和其餘語言的通訊(主要是C&C++),它容許Java代碼和其餘語言寫的代碼進行交互。
NDK:
Native Development Kit,本地開發工具包NDK和SDK很相似。在SDK中java文件是經過javac編譯成class,而後再經過dx打包成classes.dex包。在NDK中.c/.cpp文件時經過gcc等編譯成.o文件,而後經過ar打包成.so包。
如何使用ndk開發將silk文件解碼成PCM?
前面已經研究過了,只須要調用silk庫進行解碼便可,因爲silk的解碼sdk是C語言庫,因此須要使用ndk開發。接下來我要作的思路大體以下:用戶從本地選擇一個文件,而後代碼中經過獲取該文件的path,循環讀取byte數組傳入silk庫中進行解碼成PCM,而後傳入AudioTrack中進行播放。這裏咱們定義3個須要在c文件中書寫的native方法,其中nativeInit方法用來調用silk的初始化方法來建立解碼器,nativeDecode方法是用來調用silk的解碼方法,實時傳入從語音文字中獲取到的byte數組給silk庫,nativeClean是用來善後處理,好比講解碼器關閉,回收c的內存等。
private native void nativeInit();
private native int nativeDecode(byte[] silkData, short byteSize, short[] outBuffer);
private native void nativeClean();複製代碼
如何判斷某文件是silk格式的?
若是根據文件後綴名來判斷是不嚴謹的,這裏就要用到魔數知識了,通常在文件的字節碼開頭或者結尾會有。能夠經過010Editor這款16進制查看軟件進行查看字節碼數據,咱們打開全部的silk文件,其前綴都是固定的。不光是silk,其餘的文件格式也是如此,大部分都會在開頭或者結尾有一串固定的字節碼。這個固定的字節碼就叫作魔數,因此咱們只須要判斷文件的魔數是不是silk擁有的魔數便可。
接下來咱們具體在代碼中實現一下,以下圖這個dis就是本地的微信語音文件保存路徑,咱們將它讀入進輸入流中,而後獲取前10個字節,接下來和silk的魔數字節前綴HEADER數組進行對比,若是徹底同樣就說明該文件是silk文件,接下來才能進行解碼以及播放的操做。
每次應該給silk傳多大的byte數組?
其實在魔數後的2個字節是short數據,好比前面那個截圖魔數後面跟的是13 00,那麼應該如何將這個13 00轉化成音頻數據的長度呢?這裏還有大小端之分,須要將16進制的13 00 交換位置,變成00 13,而後再轉換成10進制,變成19,也就是說接下來一段音頻會有19個字節碼
至此,其中的坑點都已經明確了,咱們只須要按照思路走下去就能夠了,在java層調用c層的解碼代碼如上,循環依次獲取到每段音頻的2位16進制字節碼,而後因爲大小端字節碼順序進行替換,接下來獲取到實際的長度,而後傳遞計算出來的長度到nativeDecode方法中,其中初始化了一個outBuffer用來接收從silk傳遞回來的解碼後的數據,最後調用AudioTrack播放器進行播放已解碼的PCM數據,最後成功的播放了微信文件。只須要讓好友安裝咱們的app,就能夠聽到咱們給TA分享的微信語音啦!
核心的silk解碼代碼如上圖,咱們能夠看到其實就是調用了核心的silk庫進行解碼,而後循環接收全部的silk庫返回的byte數組,最後寫入到java層傳入的接收參數的outBuffer數組中。其他2個native方法分別用於初始化silk解碼器以及內存回收,使用malloc申請了內存就須要free來釋放,這裏代碼就不貼了,你們感興趣的話能夠進一步瞭解silk庫以及C語言的基本使用。
坑點:最後須要注意,有的6.0以上手機須要動態申請獲取本地文件權限,而後注意那個path在不一樣手機上可能會有兼容性問題,主要體如今7.0之後必須使用fileprovider來共享文件,因此要經過contentresover來解析Uri,而不能直接getPath,否則會報fileNotFoundException。
總結:今天咱們主線是嘗試使用silk庫來解碼微信的語音文件,而且使用Android系統自帶的AudioTrack播放器對silk解碼後的PCM數據進行播放。其中講解了音視頻的基礎知識、JNI和NDK開發的基礎知識、silk庫以及AudioTrack播放器的使用、查看文件的16進制字節碼方式010Editor、文件的魔數區分方法等知識,但願能對你們有所幫助!