android 使用 AudioRecord 对麦克风进行录音得到的是 pcm 格式的原始音频数据,pcm文件是不能用来播放的,需要进行编码压缩。

LAME是目前非常优秀的一种MP3编码引擎,在业界,转码成MP3格式的音频文件时,最常用的编码器就是LAME库。当达到320Kbit/s以上时,LAME编码出来的音频质量几乎可以和CD的音质相媲美,并且还能保证整个音频文件的体积非常小,因此若要在移动端平台上编码MP3文件,使用LAME便成为唯一的选择。

编译LAME库

android studio3.0+ 默认使用CMake编译native源文件,网上好多文章不合适,这里推荐两篇使用CMake编译LAME库的博客,介绍的都很详细。

音频编码

这里借用《音视频开发进阶指南:基于Android与Ios的实践》一书里对各种音频编码的介绍。

WAV编码

PCM(脉冲编码调制)是Pulse Code Modulation的缩写。前面已经介绍过PCM大致的工作流程,而WAV编码的一种实现(有多种实现方式,但是都不会进行压缩操作)就是在PCM数据格式的前面加上44字节,分别用来描述PCM的采样率、声道数、数据格式等信息。

特点:音质非常好,大量软件都支持。

适用场合:多媒体开发的中间文件、保存音乐和音效素材。

MP3编码

MP3具有不错的压缩比,使用LAME编码(MP3编码格式的一种实现)的中高码率的MP3文件,听感上非常接近源WAV文件,当然在不同的应用场景下,应该调整合适的参数以达到最好的效果。

特点:音质在128Kbit/s以上表现还不错,压缩比比较高,大量软件和硬件都支持,兼容性好。

适用场合:高比特率下对兼容性有要求的音乐欣赏。

AAC编码

AAC是新一代的音频有损压缩技术,它通过一些附加的编码技术(比如PS、SBR等),衍生出了LC-AAC、HE-AAC、HE-AAC v2三种主要的编码格式。LC-AAC是比较传统的AAC,相对而言,其主要应用于中高码率场景的编码(≥80Kbit/s);HE-AAC(相当于AAC+SBR)主要应用于中低码率场景的编码(≤80Kbit/s);而新近推出的HE-AAC v2(相当于AAC+SBR+PS)主要应用于低码率场景的编码(≤48Kbit/s)。事实上大部分编码器都设置为≤48Kbit/s自动启用PS技术,而>48Kbit/s则不加PS,相当于普通的HE-AAC。

特点:在小于128Kbit/s的码率下表现优异,并且多用于视频中的音频编码。

适用场合:128Kbit/s以下的音频编码,多用于视频中音频轨的编码。

Ogg编码

Ogg是一种非常有潜力的编码,在各种码率下都有比较优秀的表现,尤其是在中低码率场景下。Ogg除了音质好之外,还是完全免费的,这为Ogg获得更多的支持打好了基础。Ogg有着非常出色的算法,可以用更小的码率达到更好的音质,128Kbit/s的Ogg比192Kbit/s甚至更高码率的MP3还要出色。但目前因为还没有媒体服务软件的支持,因此基于Ogg的数字广播还无法实现。Ogg目前受支持的情况还不够好,无论是软件上的还是硬件上的支持,都无法和MP3相提并论。

特点:可以用比MP3更小的码率实现比MP3更好的音质,高中低码率下均有良好的表现,兼容性不够好,流媒体特性不支持。

适用场合:语音聊天的音频消息场景。

使用LAME转换pcm文件到mp3

按照前面编译lame库的博客做下来,现在工程里面已经可以通过 jni 的方式,使用lame的相关方法了。

新建 Mp3Encoder.java 文件,添加相关的 native方法。

public class Mp3Encoder {

public native int init(String pcmPath,

int audioChannels,

int bitRate,

int sampleRate,

String mp3Path);

public native void encode();

public native void destroy();

}复制代码

生成 Mp3Encoder.java 对应的头文件(.h文件,使用javah命令自动生成的)com_wyt_myapplication_Mp3Encoder.h ,要是忘了,再看看前面的两篇博客。

/* DO NOT EDIT THIS FILE - it is machine generated */

#include

/* Header for class com_wyt_myapplication_Mp3Encoder */

#ifndef _Included_com_wyt_myapplication_Mp3Encoder

#define _Included_com_wyt_myapplication_Mp3Encoder

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class: com_wyt_myapplication_Mp3Encoder

* Method: init

* Signature: (Ljava/lang/String;IIILjava/lang/String;)I

*/

JNIEXPORT jint JNICALL Java_com_wyt_myapplication_Mp3Encoder_init

(JNIEnv *, jobject, jstring, jint, jint, jint, jstring);

/*

* Class: com_wyt_myapplication_Mp3Encoder

* Method: encode

* Signature: ()V

*/

JNIEXPORT void JNICALL Java_com_wyt_myapplication_Mp3Encoder_encode

(JNIEnv *, jobject);

/*

* Class: com_wyt_myapplication_Mp3Encoder

* Method: destroy

* Signature: ()V

*/

JNIEXPORT void JNICALL Java_com_wyt_myapplication_Mp3Encoder_destroy

(JNIEnv *, jobject);

#ifdef __cplusplus

}

#endif

#endif复制代码

在 src/main/cpp 目录下新建 Mp3Encoder.cpp 文件,对刚才生成的 com_wyt_myapplication_Mp3Encoder.h 头文件里的方法进行实现。

但是方法的实现需要lame库方法的支持,如果在这个文件里完成pcm转mp3的逻辑的话,这个文件逻辑就复杂了。我们先把lame转换pcm到mp3的相关逻辑封装到心得 c/c++ 文件中,在 Mp3Encoder.cpp 文件里仅调用就行,将 java对native方法调用的实现和native方法的具体逻辑的实现分开。

也就是说整个逻辑分为了4层:java 代码——java调用native方法的实现——lame方法的封装——lame方法。对应的四个代表文件为:Mp3Encoder.java——Mp3Encoder.cpp——mp3_encode.cpp(稍后会创建并实现里面的pcm到Mp3的转换逻辑)——lame方法

这里先给除出Mp3Encoder.cpp的代码(我写完代码才写的文章),实际工作中,这一步要放到 mp3_encode.cpp 之后实现。

主要就是3各方法,分别是初始化 lame、进行编码、编码结束后资源释放。

#include "com_wyt_myapplication_Mp3Encoder.h"

#include "mp3_encoder.h"

Mp3Encoder *encoder = NULL;

JNIEXPORT jint JNICALL Java_com_wyt_myapplication_Mp3Encoder_init

(JNIEnv *env,

jobject jobj,

jstring pcmPathParam,

jint audioChannelsParam,

jint bitRateParam,

jint sampleRateParam,

jstring mp3PahtParam){

const char* pcmPath = env->GetStringUTFChars(pcmPathParam,NULL);

const char* mp3Path = env->GetStringUTFChars(mp3PahtParam,NULL);

encoder = new Mp3Encoder();

int ret = encoder->lint(pcmPath,

mp3Path,

sampleRateParam,

audioChannelsParam,

bitRateParam);

env->ReleaseStringUTFChars(mp3PahtParam, mp3Path);

env->ReleaseStringUTFChars(pcmPathParam, pcmPath);

return ret;

}

JNIEXPORT void JNICALL Java_com_wyt_myapplication_Mp3Encoder_encode

(JNIEnv *, jobject){

encoder->Encode();

}

JNIEXPORT void JNICALL Java_com_wyt_myapplication_Mp3Encoder_destroy

(JNIEnv *, jobject){

encoder->Destory();

}复制代码

mp3_encode.cpp 创建

mp3_encode.cpp 里主要是在 lame 库方法的基础上,进行简单封装,完成 pcm 到 mp3的转换。

首先定义下 mp3_encode.cpp 对应的头文件(.h文件),头文件里定义了一个 Mp3Encoder 的类,注意这是native层的C++类,和刚才定义的 Mp3Encoder.java 类没有关系。

类里面向外暴露三个方法,供 Mp3Encoder.cpp 文件的三个方法调用。

#include

#include "lame.h"

#ifndef MYAPPLICATION_MP3_ENCODER_H

#define MYAPPLICATION_MP3_ENCODER_H

#ifdef __cplusplus

extern "C" {

#endif

class Mp3Encoder {

private:

FILE *pcmFIle;

FILE *mp3File;

lame_t lameClient;

public:

Mp3Encoder();

~Mp3Encoder();

int lint(const char *pcmFilePath,

const char *mp3FilePath,

int sampleRate,

int channels,

int bitRate);

void Encode();

void Destory();

};

#ifdef __cplusplus

}

#endif

#endif复制代码

mp3_encode.cpp 文件的实现,代码看着有点长,其实很好理解,主要是初始化lame的相关参数;pcm文件读取的buffer经过lame转换,形成mp3buffer;将mp3buffer写到文件。

#include "mp3_encoder.h"

extern "C"

Mp3Encoder::Mp3Encoder(){

}

Mp3Encoder::~Mp3Encoder(){

}

int Mp3Encoder::lint(const char *pcmFilePath,

const char *mp3FilePath,

int sampleRate,

int channels,

int bitRate) {

int ret = 1;

pcmFIle = fopen(pcmFilePath, "rb");

if (pcmFIle) {

mp3File = fopen(mp3FilePath, "wb");

if (mp3File) {

//初始化lame相关参数,输入/输出采样率、音频声道数、码率

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, 128);

lame_init_params(lameClient);

ret = 0;

}

}

return ret;

}

void Mp3Encoder::Encode() {

int bufferSize = 1024 * 256;

short *buffer = new short[bufferSize / 2];

short *leftChannelBuffer = new short[bufferSize / 4];//左声道

short *rightChannelBuffer = 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) {

leftChannelBuffer[i / 2] = buffer[i];

} else {

rightChannelBuffer[i / 2] = buffer[i];

}

}

size_t writeSize = lame_encode_buffer(

lameClient,

(short int *) leftChannelBuffer,

(short int *) rightChannelBuffer,

(int) (readBufferSize / 2),

mp3_buffer,

bufferSize);

fwrite(mp3_buffer, 1, writeSize, mp3File);

}

delete [] buffer;

delete [] leftChannelBuffer;

delete [] rightChannelBuffer;

delete [] mp3_buffer;

}

void Mp3Encoder::Destory() {

if (pcmFIle){

fclose(pcmFIle);

}

if (mp3File){

fclose(mp3File);

lame_close(lameClient);

}

}复制代码

将 src/main/cpp/mp3_encoder.cpp,src/main/cpp/Mp3Encoder.cpp 添加到 CMakeLists.txt 的 add_libraty 方法中。不会的话,看一开始那两篇博客。

android 文件的读写权限别忘了,设置 manifest.xml,6.0以上的适配动态权限获取机制,这里就不说了。

到这里基本上就完成了,下面就可以在工程里使用了,比如这里我在 MainActivity 的onCreate() 里使用。

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";

private String[] permissions = new String[]{

Manifest.permission.READ_EXTERNAL_STORAGE,

Manifest.permission.WRITE_EXTERNAL_STORAGE

};

private List mPermissionList = new ArrayList<>();

private static final int MY_PERMISSIONS_REQUEST = 1001;

//采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。

public static final int SAMPLE_RATE_INHZ = 44100;

//声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。

public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;

//返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.

public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;

// Used to load the 'native-lib' library on application startup.

static {

System.loadLibrary("native-lib");

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

// Example of a call to a native method

TextView tv = (TextView) findViewById(R.id.sample_text);

tv.setText(stringFromJNI());

checkPermissions();

String pcmPath, mp3Path;

pcmPath = "/storage/emulated/0/0001.pcm";//pcm文件路径,文件要存在!

mp3Path = "/storage/emulated/0/0001.mp3";//转换后mp3文件的保存路径

Mp3Encoder encoder = new Mp3Encoder();

if(encoder.init(pcmPath,CHANNEL_CONFIG,128,SAMPLE_RATE_INHZ,mp3Path) == 0){

Log.d(TAG, "onCreate: encoder-init:success");

encoder.encode();

encoder.destroy();

Log.d(TAG, "onCreate:encode finish");

}else {

Log.d(TAG, "onCreate: encoder-init:failed");

}

}

/**

* A native method that is implemented by the 'native-lib' native library,

* which is packaged with this application.

*/

public native String stringFromJNI();

private void checkPermissions() {

// Marshmallow开始才用申请运行时权限

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

for (int i = 0; i < permissions.length; i++) {

if (ContextCompat.checkSelfPermission(this, permissions[i]) !=

PackageManager.PERMISSION_GRANTED) {

mPermissionList.add(permissions[i]);

}

}

if (!mPermissionList.isEmpty()) {

String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]);

ActivityCompat.requestPermissions(this, permissions, MY_PERMISSIONS_REQUEST);

}

}

}

@Override

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

if (requestCode == MY_PERMISSIONS_REQUEST) {

for (int i = 0; i < grantResults.length; i++) {

if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {

Log.i(TAG, permissions[i] + " 权限被用户禁止!");

}

}

// 运行时权限的申请不是本demo的重点,所以不再做更多的处理,请同意权限申请。

}

}

}复制代码

没有pcm文件?自己动手丰衣足食,自己用 AudioRecorder 写个app ,录制一个pcm!(录制pcm的代码写好了,文章还没有写,别在这等啊,我也不知道哪天会写文章。。。)

总结

本为推荐了两篇在android studio 3.0以上,使用 CMake 方式编译lame库的博客,完成lame库的集成;然后,通过jni开发,使用java代码,调用封装好音频编码逻辑的native层代码,完成pcm文件到mp3文件的转换,完整的描述了jni开发的基本流程。

android lame音频转换,音视频开发02--使用LAME库转换pcm文件到mp3相关推荐

  1. FFmpeg音视频开发实战5 iOS/Android/windows/Linux -陈超-专题视频课程

    FFmpeg音视频开发实战5 iOS/Android/windows/Linux -159618人已学习 课程介绍          咨询QQ: 347181469. 本课程适合中,从事音视频,网络通 ...

  2. Android 音视频开发学习思路

    Android 音视频开发这块目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的.只能通过一点点的学习和积累把这块的知识串联积累起来. 初级入门篇: Android 音视频开发(一) ...

  3. 给Android工程师的音视频开发学习指南

    毕业至今,之前一直从事Android开发的工作,今年5月份开始接触音视频开发相关工作,于是打算写一个音视频相关专栏,让移动端的同学,能通过这个专栏快速掌握音视频相关知识,首先带来第一篇,主要讲讲移动端 ...

  4. Android 音视频开发(一):PCM 格式音频的播放与采集

    什么是 PCM 格式 声音从模拟信号转化为数字信号的技术,经过采样.量化.编码三个过程将模拟信号数字化. 采样 顾名思义,对模拟信号采集样本,该过程是从时间上对信号进行数字化,例如每秒采集 44100 ...

  5. Android音视频开发:MediaRecorder录制音频

    Android 多媒体框架针对音频录制提供了两种方法:MediaRecorder和AudioRecord. 区别 MediaRecorder:录制的音频文件是经过压缩后的,需要设置编码器,并且录制的音 ...

  6. Android 音视频开发(一) -- 使用AudioRecord 录制PCM(录音);AudioTrack播放音频

    前言,音视频这块,确实比较难入门,本着学习的态度,我这边也跟着 Android 音视频开发入门指南 打怪升级,留下个脚印,大家共勉. 音视频 系列文章 Android 音视频开发(一) – 使用Aud ...

  7. android音频开发6,Android 音视频开发(一) : 通过三种方式绘制图片

    想要逐步入门音视频开发,就需要一步步的去学习整理,并积累.本文是音视频开发积累的第一篇. 对应的要学习的内容是:在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView ...

  8. Android 音视频开发(二):使用 AudioRecord 采集音频PCM并保存到文件(学习笔记)

    关于 AudioRecord Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风 ...

  9. Android音视频开发,详说PCM音频重采样、PCM编码

    直播伴音,两种数据能否合在一起?不能叠加在一起 会有噪音 合并以后 再去编码推流 直播的例子 客户端播放器,可以开启多个播放器 对于我们重采样 很多时候就是为了统一格式,就是为了要合并这个流,去推送, ...

最新文章

  1. java consumer_Java 8 Consumer接口
  2. c语言,字符串原地翻转
  3. openstack-Icehouse版本部署安装
  4. python编译成exe有意义吗_python工程编译成EXE
  5. java发送会议邀请邮件模板_Spring 发送邮件 HTML邮件
  6. ubuntu下动态链接库的编译和使用实例
  7. python中random库中shuffle_[宜配屋]听图阁 - 详解Python中打乱列表顺序random.shuffle()的使用方法...
  8. Oracle常用数据库操作SQL
  9. idea urule不存在
  10. USACO 2021-2022 December Contest Bronze 题解
  11. mac下的c语言贪吃蛇
  12. 解决 unkown the request
  13. 使用HTTP免费代理IP
  14. 利用Bettercap实现密码的嗅探
  15. [Poi2005]Piggy Banks小猪存钱罐
  16. MASM32编程实现窗口渐入渐出效果
  17. java 用数组存储书籍信息_有大神会编下这样的简单系统嘛,Java的,用数组存储信息!...
  18. golang实现link的过程
  19. 推荐一些学习软件编程的网站
  20. 静态qq邮箱 html scc js

热门文章

  1. mongodb nginx代理问题
  2. 制作一份计算机爱好者社团招聘简历,如何制作一份职业化的简历? -电脑资料...
  3. 高德地图搜索附近的实现
  4. 3dmax软件基础教学
  5. [附源码]SSM计算机毕业设计小锅米线点餐管理系统JAVA
  6. 集群和分布式系统简介
  7. 第5章第12节:如何让一个gif动画沿着指定的路径移动 [PowerPoint精美幻灯片实战教程]
  8. 导入jQuery:在线导入
  9. 苹果同时在新机和二手机市场发起价格战,国产手机更难了
  10. CF1634 F. Fibonacci Additions