音视频在开发中,最重要也是最复杂的就是编解码的过程,在上一篇的《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
到此音频非压缩编码和压缩编码基本讲解完毕了,若有不明白或者不正确的地方,请在下方评论区留言,望共勉之。