我們想到是否可以以傳送檔案的方式傳送過去呢?我們首先找到微信儲存在本地的語音,在tencent/MicroMsg下面,我們發現微信的語音檔案是.silk字尾名的未知格式檔案,並且手機上沒有自帶app可以開啟這種格式的語音。那麼如果我們直接找到該檔案傳送給好友,微信是否能自動識別然後讓好友聽到呢?答案是不可以的,當我們將該語音以檔案形式發過去的時候好友並不能播放,說明微信也是做過限制的。那麼有沒有辦法可以將微信的語音分享出去讓別人聽到呢?答案當然是可以的,這就是作者今天要分享的內容,如何開啟微信儲存在本地的語音檔案。想要實現該功能就必須瞭解音視訊基礎,接下來作者來講解一下。
音視訊基礎
音視訊檔案如何生成的?
如上圖,影象的原始格式是YUV/RGB。由於原始資料過大,需要對原始影象進行壓縮成H-264/VP8,壓縮過程也叫做編碼。
同理音訊的過程也大致類似,音訊的原始格式是PCM,同時也需要進行壓縮成AAC/AMR。
編碼以後將音訊和視訊封裝成一個MP4/FLV檔案,即我們常見的音視訊檔案。
音視訊檔案時如何播放的?
播放其實就是和生成相反的步驟,入上圖可看到,首先要解封裝,然後音訊和視訊分別進行解壓縮,解碼成系統能夠識別的格式,進而呼叫系統硬體進行工作。
其實安卓本身提供了錄製和播放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、檔案的魔數區分方法等知識,希望能對大家有所幫助!