Android音视频开发入门(5)使用LAME编码一个PCM文件,2024年最新Android面试精讲

target_link_libraries( # 输入你的ndk模块名

mp3_encoder

Links the target library to the log library

included in the NDK.

${log-lib})

最后我们在MianActivity下调用这个jni:

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Mp3Encoder mp3Encoder = new Mp3Encoder();

mp3Encoder.encoder();

}

点击运行后输出:

在这里插入图片描述

此时,就完成我们第一个jni项目的构建啦~

文件目录如下:

在这里插入图片描述

2. 交叉编译的原理和实践

===============================================================================

交叉编译是音视频开发中必需的,因为无论在哪个移动平台下开发,第三方库都是需要进行交叉编译的。

本节会从交叉编译的原理开始介绍,然后会在两个移动平台下编译出音视频开发常用的几个库,包括 X264、 FDK_AAC、LAME,最终将以LAME库为例进行实践,完成一个将音视频的PCM裸数据编码成MP3文件的实例,以此来证明交叉编译的重要性。

2.1 交叉编译的原理


所以交叉编译,就是 在一个平台(PC)上生成另外一个平台(Android、IOS)的可执行代码

Q:Android为什么要进行交叉编译呢?

A:即使是Android设备具有越来越强的计算能力,但是有两个原因不能在 这种嵌入式设备上进行本地编译:

  1. 还是计算能力的问题,不够全面,不够极致

  2. ARM平台上没有较好的编译环境,这导致整个编译过程异常繁琐

所以大部分的嵌入式开发平台都是提供了 本身平台交叉编译所需要的交叉工具编译链(Android提供了 Eclipse SDK、Android Studio编译器),这样开发者就能在 PC上编译出可以运行在ARM平台下的程序了。

无论是自行安装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 {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

img
img

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
img

最后

都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。

技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 PDF(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

高级UI与自定义view;
自定义view,Android开发的基本功。

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

NDK开发;
未来的方向,高薪必会。

前沿技术;
组件化,热升级,热修复,框架设计

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

11556075804)]

高级UI与自定义view;
自定义view,Android开发的基本功。

[外链图片转存中…(img-txllm8Uu-1711556075805)]

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

[外链图片转存中…(img-WzW3bchY-1711556075805)]

NDK开发;
未来的方向,高薪必会。

[外链图片转存中…(img-P96uZynC-1711556075805)]

前沿技术;
组件化,热升级,热修复,框架设计

[外链图片转存中…(img-gPaamJEh-1711556075806)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值