无论是自行安装PC上的编译器,还是下载其他平台的交叉编译链,它们都会提供下面几个工具:

  • CC

编译器,对C源文件进行编译处理,生成汇编文件

  • AS

将汇编文件生成目标文件

  • AR

打包器,用于库操作

  • LD

链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者是可执行文件

  • GDB

调试工具

  • STRIP

最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码

  • NM

查看静态库文件中的符号表

  • Objdump

查看静态库或者动态库中的方法名

2.2 Android Studio平台交叉编译工具


在编译之前,我们先看看LAME、FDK_ACC等这些的概念简介:

  • LAME

是目前非常优秀的一种MP3编译引擎,在业界,转码成 MP3格式的音频文件时,最常用的编码器就是LAME库。当达到320Kbits/s以上时,LAME编码出来的音频质量几乎可以CD的音质相媲美。并且保证整个音频文件的体积非常小。

因此若要在移动平台上编码 MP3文件,使用LAME便成为唯一选择。

  • FDK_ACC

FDK_ACC 是用来编码和解码的AAC格式音频文件的开源库。

  • X264

X264是一个开源的H.264/MPEG-4 AVC视频编码函数库,是最好的有损视频编码器之一。一般的输入的视频帧是YUV,输出是编码之后的 H264的数据包,并且支持 CBR、VBR模式,可以在编码的过程中直接改变码率的设置,这点在直播的场景中是非常实用的(直播场景下利用该特点可以做码率自适应)

了解完这些后,我们在来看看Android NDK下一些经常会用到的组件:

  • ARM、x86的交叉编译器

  • 构建系统

  • Java原生接口文件

  • C库

  • Math库

  • 最小的C++库

  • ZLib压缩库

  • POSIX线程

  • Android日志库

  • Android原生应用Api

  • OpenGL ES库

  • OpenSL ES库

2.3 AS交叉编译LAME


先去 传送门 下载好LAME的源码然后解压缩。

解压完后将 libmp3lame 文件夹下的所有的 带 .h 和带 .c的 C/C++文件 和 include 下的lame.h 复制到 JNI目录下(最好再统一放到一个新的子目录下,这边就放到了 lame子目录下),因为添加了这么多的文件,那么需要把这些文件写入到CMake的 add_library

add_library( # Sets the name of the library.

mp3_encoder

Sets the library as a shared library.

SHARED

Provides a relative path to your source file(s).

src/main/jni/Mp3Encoder.cpp

src/main/jni/lame/bitstream.c src/main/jni/lame/encoder.c

src/main/jni/lame/fft.c src/main/jni/lame/gain_analysis.c

src/main/jni/lame/id3tag.c src/main/jni/lame/lame.c

src/main/jni/lame/mpglib_interface.c src/main/jni/lame/newmdct.c

src/main/jni/lame/presets.c src/main/jni/lame/psymodel.c

src/main/jni/lame/quantize.c src/main/jni/lame/quantize_pvt.c

src/main/jni/lame/reservoir.c src/main/jni/lame/set_get.c

src/main/jni/lame/tables.c src/main/jni/lame/takehiro.c

src/main/jni/lame/util.c src/main/jni/lame/vbrquantize.c

src/main/jni/lame/VbrTag.c src/main/jni/lame/version.c)

ok,lame的源码就已经添加到我们的项目中了。但是因为文件里面一些引入的路径已经变了,所以我们要对这些引入的路径进行更改:

  1. 删除 fft.c 文件的 47 行的 include“vector/lame_intrin.h”

  2. 删除掉set_get.h的第24行

  3. 修改 util.h 文件的 570 行的 extern ieee754_float32_t fast_log2(ieee754_float32_t x)extern float fast_log2(float x)

  4. 此时还有很多文件报错,因为没有定义宏 STDC_HEADERS ,在build.gradle中添加宏定义:cFlags “-DSTDC_HEADERS”:

点个锤子后,我们打开之前 写过的Mp3Encoder.java下,进行如下修改

public class Mp3Encoder {

static {

System.loadLibrary(“mp3_encoder”);

}

public native int init(String pcmFile,int audioChannels,int bitRate,int sampleRate, String mp3Path);

public native void encoder();

public native void destroy();

}

然后给其编译,然后javah(重复上一节的操作)

产生的新的 Mp3Encoder.h替换旧的,接着在 Mp3Encoder.cpp中重写方法,它作为JNI层,是被Java层调用的:

#include “mp3_encoder.h”

#include “com_rikkatheworld_mp3encoder_studio_Mp3Encoder.h”

Mp3Encoder *encoder = NULL;

extern “C” {

#define LOG_TAG “Mp3Encoder”

#define LOGI(…) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,VA_ARGS)

//实例化Mp3Encoder,然后调用初始方法

JNIEXPORT jint JNICALL Java_com_rikkatheworld_mp3encoder_studio_Mp3Encoder_init

(JNIEnv *env, jobject, jstring pcmPathParam, jint channels, jint bitRate, jint sampleRate,

jstring mp3PathParam) {

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

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

encoder = new Mp3Encoder();

int ret = encoder->Init(pcmPath, mp3Path, sampleRate, channels, bitRate);

env->ReleaseStringUTFChars(mp3PathParam, mp3Path);

env->ReleaseStringUTFChars(pcmPathParam, pcmPath);

return ret;

}

JNIEXPORT void JNICALL Java_com_rikkatheworld_mp3encoder_studio_Mp3Encoder_encoder

(JNIEnv *, jobject) {

encoder->Encode();

}

JNIEXPORT void JNICALL Java_com_rikkatheworld_mp3encoder_studio_Mp3Encoder_destroy

(JNIEnv *, jobject) {

encoder->Destory();

}

}

我们在JNI层中调用了 native层的代码,我们要去 jni下创建两个文件 mp3_encoder.hmp3_encoder.cpp

我们先来编写 mp3_encoder.h,定义变量和方法:

#ifndef MP3ENCODER_MP3_ENCODER_H

#define MP3ENCODER_MP3_ENCODER_H

#include “lame/lame.h”

extern “C” {

class Mp3Encoder {

private:

FILE *pcmFile;

FILE *mp3File;

lame_t lameClient;

public:

Mp3Encoder();

~Mp3Encoder();

int Init(const char *pcmFilePath, const char *mp3FilePath, int sampleRate, int channels,

int bitRat);

void Encode();

void Destory();

};

#endif //MP3ENCODER_MP3_ENCODER_H

}

接着我们编写 mp3_encoder.cpp, 它会使用到 lame库 里的一些方法:

#include “mp3_encoder.h”

#include <jni.h>

extern “C”

/**

  • 以二进制文件的方式打开PCM文件,以写入二进制文件的方式打开MP3文件,然后初始化LAME

*/

int

Mp3Encoder::Init(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) {

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;

}

}

return ret;

}

/**

  • 函数主体是一个循环,每次都会读取一段bufferSize大小的PCM数据buffer,然后再编码该buffer

  • 但是在编码buffer之前得把该buffer的左右声道拆分开,再送入到 lame编码器

  • 最后将编码的数据写入到mp3文件中

*/

void Mp3Encoder::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, leftBuffer, rightBuffer,

(int) (readBufferSize / 2), mp3_buffer, bufferSize);

fwrite(mp3_buffer, 1, wroteSize, mp3File);

}

delete[] buffer;

delete[] leftBuffer;

delete[] rightBuffer;

delete[] mp3_buffer;

}

void Mp3Encoder::Destory() {

if (pcmFile) {

fclose(pcmFile);

}

if (mp3File) {

fclose(mp3File);

}

}

Mp3Encoder::Mp3Encoder() {

}

Mp3Encoder::~Mp3Encoder() {

}

这些代码其实我们不用特别的了解,只需了解并且跑通就可以了。

到这里,我们的 JNI层、Native层都编译完了。

接下来就是Java层的调用了,我们先下载一个 PCM文件到手机下,然后在MainActivity下这样写:

public class MainActivity extends AppCompatActivity {

private static final String TAG = “MainActivity”;

priva
te 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;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

checkPermissions();

String pcmPath, mp3Path;

pcmPath = “/storage/emulated/0/16k.pcm”;//pcm文件路径,文件要存在!

mp3Path = “/storage/emulated/0/16k1.mp3”;//转换后mp3文件的保存路径

Mp3Encoder mp3Encoder = new Mp3Encoder();

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

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

mp3Encoder.encoder();

mp3Encoder.destroy();

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

} else {

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

}

}

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] + " 权限被用户禁止!");

}

}

) {

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] + " 权限被用户禁止!");

}

}

Android音视频开发入门(5)使用LAME编码一个PCM文件,为了跳槽强刷1000道Android真题相关推荐

  1. 为了跳槽强刷1000道Android真题,面试资料分享

    前言 近日,字节跳动正式启动了2021届秋季校园招聘,为应届毕业生开放超过6000个工作岗位.这一数字超过了该公司往年秋招规模,并与其今年春招规模持平.全年校招人数共计超过1万2千人,远高于同类型互联 ...

  2. 安卓音视频开发!为了跳槽强刷1000道Android真题,大厂直通车!

    为什么要做职业规划? 我们先聊聊第一个话题,为什么要做职业规划? 首先,我们要知道职业规划是什么,也就是如何持续选择适合自己发展的工作的过程. 职业规划其实就是对职业生涯乃至人生进行持续的.系统的.计 ...

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

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

  4. Android 音视频开发入门指南

    最近收到很多网友通过邮件或者留言说想学习音视频开发,该如何入门,我今天专门写篇文章统一回复下吧. 音视频这块,目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的,希望我后面能挤出时间整 ...

  5. Android音视频开发入门指南

    <Android 音视频从入门到提高 -- 任务列表> 1. 在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView,SurfaceView,自定义 Vi ...

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

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

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

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

  8. 23最新《Android音视频开发进阶指南》,音视频开发者速领

    作为Android开发程序员,我们时刻站在互联网的前端,而音视频作为现在乃至未来几年一个强劲的风口,吸引了许多程序员的关注. 那么音视频开发的行业现状究竟如何呢?我们又该怎样入门呢?请看下文: 音视频 ...

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

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

最新文章

  1. QT 防止FTP 上传软件在断连处 Crash
  2. 笔记本电脑有蓝牙连接功能吗_百元蓝牙无线键盘推荐——罗技K380
  3. 多项式对数函数ln f(x)
  4. java观察者模式在spring中的应用_在Spring中使用观察者模式
  5. 让oracle跑得更快——oracle 10g性能分析与优化思路,[让Oracle跑得更快.Oracle.10g性能分析与优化思路]概要1.doc...
  6. php环形链表,PHP环形链表实现方法示例
  7. linux进程控制(一)--unix环境高级编程读书笔记
  8. Log4j框架配置文件log4j.properties配置使用详解
  9. 计算机显微视觉相关概念,计算机视觉热门科研!基于深度神经网络的蛋白质智能显微分类系统,已开启!...
  10. 论文的重复率修改方法
  11. GD32VF103启动流程分析
  12. JUnit 单元测试
  13. 操作无法完成 因为文件已在system中打开
  14. 如何选择适合的大数据分析软件
  15. Android欢迎页面以及引导页面
  16. perf常用命令(perf top perf record perf stat)
  17. 工作习惯决定事业成败
  18. matplotlib 绘制三角函数图像
  19. 解决必应biying搜索跳转到百度www.baidu.com搜索的解决方法
  20. matlab/simulink学习的笔记都总结在这里

热门文章

  1. Debian改变网卡名称
  2. [DX10游戏教程(C++)]教程1:在Visual Studio 2012中配置DirectX 10
  3. 贝叶斯分析助你成为优秀的调参侠:自动化搜索物理模型的参数空间
  4. int正数和负数的原码、反码、补码
  5. js 伪造referer_javascript操作referer详细解析
  6. 深入理解计算机系统--链接
  7. spring中自定义注解(annotation)与AOP中获取注解___使用aspectj的@Around注解实现用户操作和操作结果日志
  8. Oracle性能优化概述
  9. 37.深度解密三十七:网络营销推广之百度经验营销全过程步骤讲解
  10. 听音乐用什么蓝牙耳机好?音质好的tws蓝牙耳机推荐