音視頻在開發中,最重要也是最複雜的就是編解碼的過程,在上一篇的《Android音視頻開發:踩一踩「門檻」》中,咱們說音頻的編碼根據大小劃分有兩種:壓縮編碼和非壓縮編碼,那究竟是怎麼實現的這兩中編碼的呢?這一次就詳細瞭解Android中如何使用這兩種方式進行音頻編碼java
這裏先回顧一下音頻的壓縮編碼和非壓縮編碼:android
由於非壓縮編碼實在是太大了,因此咱們生活中所接觸的音頻編碼格式都是壓縮編碼,並且是有損壓縮,好比 MP3或AAC。
那如何操做PCM數據呢?Android SDK中提供了一套對PCM操做的API:AudioRecord
和 AudioTrack
;c++
因爲AudioRecord(錄音)
和 AudioTrack(播放)
操做過於底層並且過於複雜,因此Android SDK 還提供了一套與之對應更加高級的API:MediaRecorder(錄音)
和MediaPlayer(播放)
,用於音視頻的操做,固然其更加簡單方便。咱們這裏只介紹前者,經過它來實現對PCM數據的操做。git
對於壓縮編碼,咱們則經過MediaCodec
和Lame
來分別實現AAC音頻和Mp3音頻壓縮編碼。話很少說,請往下看!github
因爲AudioRecord
更加底層,可以更好的而且直接的管理經過音頻錄製硬件設備錄製後的PCM數據,因此對數據處理更加靈活,可是同時也須要咱們本身處理編碼的過程。算法
AudioRecord的使用流程大體以下:緩存
AudioRecord
startRecording
開始錄製AudioRecord
將錄製的音頻數據從緩存中讀取並寫入文件在使用AudioRecord
前須要先注意添加RECORD_AUDIO
錄音權限。bash
咱們先看看AudioRecord
構造方法session
public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes) 複製代碼
audioSource,從字面意思可知音頻來源,由MediaRecorder.AudioSource
提供,主要有如下內容app
· CAMCORDER 與照相機方向相同的麥克風音頻源
· DEFAULT 默認
· MIC 麥克風音頻源
· VOICE_CALL 語音通話
這裏採用MIC
麥克風音頻源
sampleRateInHz,採樣率,即錄製的音頻每秒鐘會有多少次採樣,可選用的採樣頻率列表爲:8000、16000、22050、24000、32000、44100、48000等,通常採用人能聽到最大音頻的2倍,也就是44100Hz。
channelConfig,聲道數的配置,可選值以常量的形式配置在類AudioFormat中,經常使用的是CHANNEL_IN_MONO(單聲道)、CHANNEL_IN_STEREO(雙聲道)
audioFormat,採樣格式,可選值以常量的形式定義在類AudioFormat中,分別爲ENCODING_PCM_16BIT(16bit)、ENCODING_PCM_8BIT(8bit),通常採用16bit。
bufferSizeInBytes,其配置的是AudioRecord內部的音頻緩衝區的大小,可能會由於生產廠家的不一樣而有所不一樣,爲了方便AudioRecord提供了一個獲取該值最小緩衝區大小的方法getMinBufferSize
。
public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat) 複製代碼
在開發過程當中需使用getMinBufferSize
此方法計算出最小緩存大小。
首先經過調用getState
判斷AudioRecord是否初始化成功,而後經過startRecording
切換成錄製狀態
if (null!=audioRecord && audioRecord?.state!=AudioRecord.STATE_UNINITIALIZED){
audioRecord?.startRecording()
}
複製代碼
thread = Thread(Runnable {
writeData2File()
})
thread?.start()
複製代碼
開啓錄音線程將錄音數據經過AudioRecord寫入文件
private fun writeData2File() {
var ret = 0
val byteArray = ByteArray(bufferSizeInBytes)
val file = File(externalCacheDir?.absolutePath + File.separator + filename)
if (file.exists()) {
file.delete()
} else {
file.createNewFile()
}
val fos = FileOutputStream(file)
while (status == Status.STARTING) {
ret = audioRecord?.read(byteArray, 0, bufferSizeInBytes)!!
if (ret!=AudioRecord.ERROR_BAD_VALUE || ret!=AudioRecord.ERROR_INVALID_OPERATION|| ret!=AudioRecord.ERROR_DEAD_OBJECT){
fos.write(byteArray)
}
}
fos.close()
}
複製代碼
首先中止錄製
if (null!=audioRecord && audioRecord?.state!=AudioRecord.STATE_UNINITIALIZED){
audioRecord?.stop()
}
複製代碼
而後中止線程
if (thread!=null){
thread?.join()
thread =null
}
複製代碼
最後釋放AudioRecord
if (audioRecord != null) {
audioRecord?.release()
audioRecord = null
}
複製代碼
經過以上一個流程以後,就能夠獲得一個非壓縮編碼的PCM數據了。
可是這個數據在音樂播放器上通常是播放不了的,那麼怎麼驗證我是否錄製成功呢?固然是使用咱們的AudioTrack
進行播放看看是否是剛剛咱們錄製的聲音了。
因爲AudioTrack
是由Android SDK提供比較底層的播放API,也只能操做PCM裸數據,經過直接渲染PCM數據進行播放。固然若是想要使用AudioTrack
進行播放,那就須要自行先將壓縮編碼格式文件解碼。
AudioTrack的使用流程大體以下:
AudioTrack
play
開始播放AudioTrack
緩存區寫入音頻數據咱們來看看AudioTrack
的構造方法
public AudioTrack (int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode, int sessionId) 複製代碼
streamType,Android手機上提供音頻管理策略,按下音量鍵咱們會發現由媒體聲音管理,鬧鈴聲音管理,通話聲音管理等等,當系統有多個進程須要播放音頻的時候,管理策略會決定最終的呈現效果,該參數的可選值將以常量的形式定義在類AudioManager中,主要包括如下內容:
· STREAM_VOCIE_CALL:電話聲音
· STREAM_SYSTEM:系統聲音
· STREAM_RING:鈴聲
· STREAM_MUSCI:音樂聲
· STREAM_ALARM:警告聲
· STREAM_NOTIFICATION:通知聲
由於這裏是播放音頻,因此咱們選擇STREAM_MUSCI
。
sampleRateInHz,採樣率,即播放的音頻每秒鐘會有多少次採樣,可選用的採樣頻率列表爲:8000、16000、22050、24000、32000、44100、48000等,通常採用人能聽到最大音頻的2倍,也就是44100Hz。
channelConfig,聲道數的配置,可選值以常量的形式配置在類AudioFormat中,經常使用的是CHANNEL_IN_MONO(單聲道)、CHANNEL_IN_STEREO(立體雙聲道)
audioFormat,採樣格式,可選值以常量的形式定義在類AudioFormat中,分別爲ENCODING_PCM_16BIT(16bit)、ENCODING_PCM_8BIT(8bit),通常採用16bit。
bufferSizeInBytes,其配置的是AudioTrack內部的音頻緩衝區的大小,可能會由於生產廠家的不一樣而有所不一樣,爲了方便AudioTrack提供了一個獲取該值最小緩衝區大小的方法getMinBufferSize
。
mode,播放模式,AudioTrack提供了兩種播放模式,可選的值以常量的形式定義在類AudioTrack中,一個是MODE_STATIC,須要一次性將全部的數據都寫入播放緩衝區中,簡單高效,一般用於播放鈴聲、系統提醒的音頻片斷;另外一個是MODE_STREAM,須要按照必定的時間間隔不間斷地寫入音頻數據,理論上它能夠應用於任何音頻播放的場景。
sessionId,AudioTrack都須要關聯一個會話Id,在建立AudioTrack時可直接使用AudioManager.AUDIO_SESSION_ID_GENERATE
,或者在構造以前經過AudioManager.generateAudioSessionId
獲取。
上面這種構造方法已經被棄用了,如今基本使用以下構造(最小skd 版本須要>=21),參數內容與上基本一致:
public AudioTrack (AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId) 複製代碼
經過AudioAttributes.Builder
設置參數streamType
var audioAttributes = AudioAttributes.Builder()
.setLegacyStreamType(AudioManager.STREAM_MUSIC)
.build()
複製代碼
經過AudioFormat.Builder
設置channelConfig,sampleRateInHz,audioFormat參數
var mAudioFormat = AudioFormat.Builder()
.setChannelMask(channel)
.setEncoding(audioFormat)
.setSampleRate(sampleRate)
.build()
複製代碼
首先經過調用getState
判斷AudioRecord是否初始化成功,而後經過play
切換成錄播放狀態
if (null!=audioTrack && audioTrack?.state != AudioTrack.STATE_UNINITIALIZED){
audioTrack?.play()
}
複製代碼
開啓播放線程
thread= Thread(Runnable {
readDataFromFile()
})
thread?.start()
複製代碼
將數據不斷的送入緩存區並經過AudioTrack播放
private fun readDataFromFile() {
val byteArray = ByteArray(bufferSizeInBytes)
val file = File(externalCacheDir?.absolutePath + File.separator + filename)
if (!file.exists()) {
Toast.makeText(this, "請先進行錄製PCM音頻", Toast.LENGTH_SHORT).show()
return
}
val fis = FileInputStream(file)
var read: Int
status = Status.STARTING
while ({ read = fis.read(byteArray);read }() > 0) {
var ret = audioTrack?.write(byteArray, 0, bufferSizeInBytes)!!
if (ret == AudioTrack.ERROR_BAD_VALUE || ret == AudioTrack.ERROR_INVALID_OPERATION || ret == AudioManager.ERROR_DEAD_OBJECT) {
break
}
}
fis.close()
}
複製代碼
首先中止播放
if (audioTrack != null && audioTrack?.state != AudioTrack.STATE_UNINITIALIZED) {
audioTrack?.stop()
}
複製代碼
而後中止線程
if (thread!=null){
thread?.join()
thread =null
}
複製代碼
最後釋放AudioTrack
if (audioTrack != null) {
audioTrack?.release()
audioTrack = null
}
複製代碼
通過這樣幾個步驟,咱們就能夠聽到剛剛咱們錄製的PCM數據聲音啦!這就是使用Android提供的AudioRecord
和AudioTrack
對PCM數據進行操做。
可是僅僅這樣是不夠的,由於咱們生活中確定不是使用PCM進行音樂播放,那麼怎麼才能讓音頻在主流播放器上播放呢?這就須要咱們進行壓縮編碼了,好比mp3或aac壓縮編碼格式。
AAC
壓縮編碼是一種高壓縮比的音頻壓縮算法,AAC壓縮比一般爲18:1;採樣率範圍一般是8KHz~96KHz,這個範圍比MP3更廣一些(MP3的範圍通常是:16KHz~48KHz),因此在16bit的採樣格式上比MP3更精細。
方便咱們處理AAC編碼,Android SDK中提供了MediaCodec
API,能夠將PCM數據編碼成AAC數據。大概須要如下幾個步驟:
MediaCodec
MediaCodec
配置音頻參數MediaCodec
將緩衝區的數據進行編碼並寫入文件經過MediaCodec.createEncoderByType
建立編碼MediaCodec
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
複製代碼
// 配置採樣率和聲道數
mediaFormat = MediaFormat.createAudioFormat(MINE_TYPE,sampleRate,channel)
// 配置比特率
mediaFormat?.setInteger(MediaFormat.KEY_BIT_RATE,bitRate)
// 配置PROFILE,其中屬AAC-LC兼容性最好
mediaFormat?.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
// 最大輸入大小
mediaFormat?.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 10 * 1024)
mediaCodec!!.configure(mediaFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE)
mediaCodec?.start()
inputBuffers = mediaCodec?.inputBuffers
outputBuffers = mediaCodec?.outputBuffers
複製代碼
啓動線程,循環讀取PCM數據送入緩衝區
thread = Thread(Runnable {
val fis = FileInputStream(pcmFile)
fos = FileOutputStream(aacFile)
var read: Int
while ({ read = fis.read(byteArray);read }() > 0) {
encode(byteArray)
}
})
thread?.start()
複製代碼
將送入的PCM數據經過MediaCodec
進行編碼,大體流程以下:
data
,並添加ADTS頭部信息(有7byte)outputBuffer
編碼後數據寫入data
(data有7byte偏移)data
寫入文件private fun encode(byteArray: ByteArray){
mediaCodec?.run {
//返回要用有效數據填充的輸入緩衝區的索引, -1 無限期地等待輸入緩衝區的可用性
val inputIndex = dequeueInputBuffer(-1)
if (inputIndex > 0){
// 根據索引獲取可用輸入緩存區
val inputBuffer = this@AACEncoder.inputBuffers!![inputIndex]
// 清空緩衝區
inputBuffer.clear()
// 將pcm數據放入緩衝區
inputBuffer.put(byteArray)
// 提交放入數據緩衝區索引以及大小
queueInputBuffer(inputIndex,0,byteArray.size,System.nanoTime(),0)
}
// 指定編碼器緩衝區中有效數據範圍
val bufferInfo = MediaCodec.BufferInfo()
// 獲取輸出緩衝區索引
var outputIndex = dequeueOutputBuffer(bufferInfo,0)
while (outputIndex>0){
// 根據索引獲取可用輸出緩存區
val outputBuffer =this@AACEncoder.outputBuffers!![outputIndex]
// 測量輸出緩衝區大小
val bufferSize = bufferInfo.size
// 輸出緩衝區實際大小,ADTS頭部長度爲7
val bufferOutSize = bufferSize+7
// 指定輸出緩存區偏移位置以及限制大小
outputBuffer.position(bufferInfo.offset)
outputBuffer.limit(bufferInfo.offset+bufferSize)
// 建立輸出空數據
val data = ByteArray(bufferOutSize)
// 向空數據先增長ADTS頭部
addADTStoPacket(data, bufferOutSize)
// 將編碼輸出數據寫入已加入ADTS頭部的數據中
outputBuffer.get(data,7,bufferInfo.size)
// 從新指定輸出緩存區偏移
outputBuffer.position(bufferInfo.offset)
// 將獲取的數據寫入文件
fos?.write(data)
// 釋放輸出緩衝區
releaseOutputBuffer(outputIndex,false)
// 從新獲取輸出緩衝區索引
outputIndex=dequeueOutputBuffer(bufferInfo,0)
}
}
}
複製代碼
編碼完成後,必定要釋放全部資源,首先關閉輸入輸出流
fos?.close()
fis.close()
複製代碼
中止編碼
if (mediaCodec!=null){
mediaCodec?.stop()
}
複製代碼
而後就是關閉線程
if (thread!=null){
thread?.join()
thread =null
}
複製代碼
最後釋放MediaCodec
if (mediaCodec!=null){
mediaCodec?.release()
mediaCodec = null
mediaFormat = null
inputBuffers = null
outputBuffers = null
}
複製代碼
經過以上一個流程,咱們就能夠獲得一個AAC壓縮編碼的音頻文件,能夠聽一聽是否是本身剛剛錄製的。我聽了一下我本身唱的一首歌,以爲個人仍是能夠的嘛,也不是那麼五音不全~~
雖然咱們經過壓縮編碼生成了AAC音頻文件,可是有個問題:畢竟AAC音頻不是主流的音頻文件呀,咱們最多見的是MP3的嘛,可不能夠將PCM編碼成MP3呢?
固然是能夠的,可是Android SDK沒有直接提供這樣的API,只能使用Android NDK,經過交叉編譯其餘C或C++庫來進行實現。
Android NDK 是由Google提供一個工具集,可以讓您使用 C 和 C++ 等語言實現應用。
Android NDK 通常有兩個用途,一個是進一步提高設備性能,以下降延遲,或運行計算密集型應用,如遊戲或物理模擬;另外一個是重複使用您本身或其餘開發者的 C 或 C++ 庫。固然咱們使用最多的應該仍是後者。
想使用Android NDK調試代碼須要如下工具:
能夠進入Tools > SDK Manager > SDK Tools 選擇 NDK (Side by side) 和 CMake 應用安裝
在應用以上選項以後,咱們能夠看到SDK的目錄中多了一個ndk-bundle
的文件夾,大體目錄結構以下
ndk-build:該Shell腳本是Android NDK構建系統的起始點,通常在項目中僅僅執行這一個命令就能夠編譯出對應的動態連接庫了,後面的編譯mp3lame 就會使用到。
platforms:該目錄包含支持不一樣Android目標版本的頭文件和庫文件,NDK構建系統會根據具體的配置來引用指定平臺下的頭文件和庫文件。
toolchains:該目錄包含目前NDK所支持的不一樣平臺下的交叉編譯器——ARM、x8六、MIPS,其中比較經常使用的是ARM和x86。不管是哪一個平臺都會提供如下工具:
·CC:編譯器,對C源文件進行編譯處理,生成彙編文件。
·AS:將彙編文件生成目標文件(彙編文件使用的是指令助記符,AS將它翻譯成機器碼)。
·AR:打包器,用於庫操做,能夠經過該工具從一個庫中刪除或者增長目標代碼模塊。
·LD:連接器,爲前面生成的目標代碼分配地址空間,將多個目標文件連接成一個庫或者是可執行文件。
·GDB:調試工具,能夠對運行過程當中的程序進行代碼調試工做。
·STRIP:以最終生成的可執行文件或者庫文件做爲輸入,而後消除掉其中的源碼。
·NM:查看靜態庫文件中的符號表。
·Objdump:查看靜態庫或者動態庫的方法簽名。
瞭解Android NDK 以後,就可新建一個支持C/C++ 的Android項目了:
LAME是一個開源的MP3音頻壓縮庫,當前是公認有損質量MP3中壓縮效果最好的編碼器,因此咱們選擇它來進行壓縮編碼,那如何進行壓縮編碼呢?主流的由兩種方式:
下面就詳細講解這兩種方式
配置Cmake以後能夠直接將Lame代碼運行於Android中
下載Lame-3.100並解壓大概獲得以下目錄
而後將裏面的libmp3lame
文件夾拷貝到咱們上面建立的支持c/c++項目,刪除其中的i386和vector文件夾,以及其餘非.c 和 .h 後綴的文件
須要將如下文件進行修改,不然會報錯
extern ieee754_float32_t fast_log2(ieee754_float32_t x)
複製代碼
替換成
extern float fast_log2(float x)
複製代碼
HAVE_STRCHR
和HAVE_MEMCPY
註釋#ifdef STDC_HEADERS
# include <stddef.h>
# include <stdlib.h>
# include <string.h>
# include <ctype.h>
#else
/*# ifndef HAVE_STRCHR
# define strchr index
# define strrchr rindex
# endif
*/
char *strchr(), *strrchr();
/*# ifndef HAVE_MEMCPY
# define memcpy(d, s, n) bcopy ((s), (d), (n))
# endif*/
#endif
複製代碼
//#include "vector/lame_intrin.h"
複製代碼
#include <lame.h>
複製代碼
替換成
#include "lame.h"
複製代碼
首先在本身的包下(我這裏是com.coder.media
,這個很重要,後面會用到),新建Mp3Encoder
的文件,大概以下幾個方法
class Mp3Encoder {
companion object {
init {
System.loadLibrary("mp3encoder")
}
}
external fun init( pcmPath: String, channel: Int, bitRate: Int, sampleRate: Int, mp3Path: String ): Int
external fun encode()
external fun destroy()
}
複製代碼
在cpp目錄下新建兩個文件
這兩個文件中可能會提示錯誤異常,先不要管它,這是由於咱們尚未配置CMakeList.txt
致使的。
在mp3-encoder.h
中定義三個變量
FILE* pcmFile;
FILE* mp3File;
lame_t lameClient;
複製代碼
而後在mp3-encoder.c
中分別實現咱們在Mp3Encoder
中定義的三個方法
首先導入須要的文件
#include <jni.h>
#include <string>
#include "android/log.h"
#include "libmp3lame/lame.h"
#include "mp3-encoder.h"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "mp3-encoder", __VA_ARGS__)
複製代碼
而後實現init方法
extern "C" JNIEXPORT jint JNICALL Java_com_coder_media_Mp3Encoder_init(JNIEnv *env, jobject obj, jstring pcmPathParam, jint channels, jint bitRate, jint sampleRate, jstring mp3PathParam) {
LOGD("encoder init");
int ret = -1;
const char* pcmPath = env->GetStringUTFChars(pcmPathParam, NULL);
const char* mp3Path = env->GetStringUTFChars(mp3PathParam, NULL);
pcmFile = fopen(pcmPath,"rb");
if (pcmFile){
mp3File = fopen(mp3Path,"wb");
if (mp3File){
lameClient = lame_init();
lame_set_in_samplerate(lameClient, sampleRate);
lame_set_out_samplerate(lameClient,sampleRate);
lame_set_num_channels(lameClient,channels);
lame_set_brate(lameClient,bitRate);
lame_init_params(lameClient);
ret = 0;
}
}
env->ReleaseStringUTFChars(mp3PathParam, mp3Path);
env->ReleaseStringUTFChars(pcmPathParam, pcmPath);
return ret;
}
複製代碼
這個方法的做用就是將咱們的音頻參數信息送入lameClient
須要注意我這裏的方法Java_com_coder_media_Mp3Encoder_init
中的com_coder_media
須要替換成你本身的對應包名,下面的encode和destroy也是如此,切記!!!
實現經過lame
編碼encode
extern "C" JNIEXPORT void JNICALL Java_com_coder_media_Mp3Encoder_encode(JNIEnv *env, jobject obj) {
LOGD("encoder encode");
int bufferSize = 1024 * 256;
short* buffer = new short[bufferSize / 2];
short* leftBuffer = new short[bufferSize / 4];
short* rightBuffer = new short[bufferSize / 4];
unsigned char* mp3_buffer = new unsigned char[bufferSize];
size_t readBufferSize = 0;
while ((readBufferSize = fread(buffer, 2, bufferSize / 2, pcmFile)) > 0) {
for (int i = 0; i < readBufferSize; i++) {
if (i % 2 == 0) {
leftBuffer[i / 2] = buffer[i];
} else {
rightBuffer[i / 2] = buffer[i];
}
}
size_t wroteSize = lame_encode_buffer(lameClient, (short int *) leftBuffer, (short int *) rightBuffer,
(int)(readBufferSize / 2), mp3_buffer, bufferSize);
fwrite(mp3_buffer, 1, wroteSize, mp3File);
}
delete[] buffer;
delete[] leftBuffer;
delete[] rightBuffer;
delete[] mp3_buffer;
}
複製代碼
最後釋放資源
extern "C" JNIEXPORT void JNICALL
Java_com_coder_media_Mp3Encoder_destroy(JNIEnv *env, jobject obj) {
LOGD("encoder destroy");
if(pcmFile) {
fclose(pcmFile);
}
if(mp3File) {
fclose(mp3File);
lame_close(lameClient);
}
}
複製代碼
打開CPP目錄下的CMakeList.txt文件,向其中添加以下代碼
// 引入目錄
include_directories(libmp3lame)
// 將libmp3lame下全部文件路徑賦值給 SRC_LIST
aux_source_directory(libmp3lame SRC_LIST)
// 加入libmp3lame全部c文件
add_library(mp3encoder
SHARED
mp3-encoder.cpp ${SRC_LIST})
複製代碼
而且向target_link_libraries
添加mp3encoder
target_link_libraries(
mp3encoder
native-lib
${log-lib})
複製代碼
修改CMakeList.txt以後,點擊右上角Sync Now
就能夠看到咱們mp3-encoder.cpp
和mp3-encoder.h
中的錯誤提示不見了,至此已基本完成
而後在咱們的代碼中調用Mp3Encoder
中的方法就能夠將PCM
編碼成Mp3
了
private fun encodeAudio() {
var pcmPath = File(externalCacheDir, "record.pcm").absolutePath
var target = File(externalCacheDir, "target.mp3").absolutePath
var encoder = Mp3Encoder()
if (!File(pcmPath).exists()) {
Toast.makeText(this, "請先進行錄製PCM音頻", Toast.LENGTH_SHORT).show()
return
}
var ret = encoder.init(pcmPath, 2, 128, 44100, target)
if (ret == 0) {
encoder.encode()
encoder.destroy()
Toast.makeText(this, "PCM->MP3編碼完成", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Lame初始化失敗", Toast.LENGTH_SHORT).show()
}
}
複製代碼
ndk-build編譯Lame,其實就是生成一個.so後綴的動態文件庫供你們使用
jni
文件夾jni
下Android.mk
文件其中有幾個重要配置說明以下
· LOCAL_PATH:=$(call my-dir),返回當前文件在系統中的路徑,Android.mk文件開始時必須定義該變量。
· include$(CLEAR_VARS),代表清除上一次構建過程的全部全局變量,由於在一個Makefile編譯腳本中,會使用大量的全局變量,使用這行腳本代表須要清除掉全部的全局變量
· LOCAL_MODULE,編譯目標項目名,若是是so文件,則結果會以lib項目名.so呈現
· LOCAL_SRC_FILES,要編譯的C或者Cpp的文件,注意這裏不須要列舉頭文件,構建系統會自動幫助開發者依賴這些文件。
· LOCAL_LDLIBS,所依賴的NDK動態和靜態庫。
· Linclude $(BUILD_SHARED_LIBRARY),構建動態庫
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := mp3encoder
LOCAL_SRC_FILES := mp3-encoder.cpp \
libmp3lame/bitstream.c \
libmp3lame/psymodel.c \
libmp3lame/lame.c \
libmp3lame/takehiro.c \
libmp3lame/encoder.c \
libmp3lame/quantize.c \
libmp3lame/util.c \
libmp3lame/fft.c \
libmp3lame/quantize_pvt.c \
libmp3lame/vbrquantize.c \
libmp3lame/gain_analysis.c \
libmp3lame/reservoir.c \
libmp3lame/VbrTag.c \
libmp3lame/mpglib_interface.c \
libmp3lame/id3tag.c \
libmp3lame/newmdct.c \
libmp3lame/set_get.c \
libmp3lame/version.c \
libmp3lame/presets.c \
libmp3lame/tables.c \
LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid -lm -pthread -L$(SYSROOT)/usr/lib
include $(BUILD_SHARED_LIBRARY)
複製代碼
Application.mk
APP_ABI := all
APP_PLATFORM := android-21
APP_OPTIM := release
APP_STL := c++_static
複製代碼
最終效果以下:
最後在當前目錄下以command命令運行ndk-build
/home/relo/Android/Sdk/ndk-bundle/ndk-build
複製代碼
若是不出意外,就能夠在jni
同級目錄libs
下面看到各個平臺的so文件
將so文件拷貝至咱們普通Android項目jniLibs下面,而後在本身的包下(我這裏是com.coder.media
),新建如上Mp3Encoder
的文件,最後在須要使用編碼MP3的位置使用Mp3Encoder
中的三個方法就能夠了。
可是須要注意的是須要在app下的build.gradle配置與jniLibs下對應的APP_ABI
到此音頻非壓縮編碼和壓縮編碼基本講解完畢了,若有不明白或者不正確的地方,請在下方評論區留言,望共勉之。