Android音视频开发入门(5)使用LAME编码一个PCM文件,2024年最新Android面试精讲_lame android-程序员宅基地

技术标签: 2024年程序员学习  音视频  android  pcm  

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学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_58163367/article/details/137094023

智能推荐

hdu 1229 还是A+B(水)-程序员宅基地

文章浏览阅读122次。还是A+BTime Limit: 2000/1000 MS (Java/Others)Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 24568Accepted Submission(s): 11729Problem Description读入两个小于10000的正整数A和B,计算A+B。...

http客户端Feign——日志配置_feign 日志设置-程序员宅基地

文章浏览阅读419次。HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息。FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。BASIC:仅记录请求的方法,URL以及响应状态码和执行时间。NONE:不记录任何日志信息,这是默认值。配置Feign日志有两种方式;方式二:java代码实现。注解中声明则代表某服务。方式一:配置文件方式。_feign 日志设置

[转载]将容器管理的持久性 Bean 用于面向服务的体系结构-程序员宅基地

文章浏览阅读155次。将容器管理的持久性 Bean 用于面向服务的体系结构本文将介绍如何使用 IBM WebSphere Process Server 对容器管理的持久性 (CMP) Bean的连接和持久性逻辑加以控制,使其可以存储在非关系数据库..._javax.ejb.objectnotfoundexception: no such entity!

基础java练习题(递归)_java 递归例题-程序员宅基地

文章浏览阅读1.5k次。基础java练习题一、递归实现跳台阶从第一级跳到第n级,有多少种跳法一次可跳一级,也可跳两级。还能跳三级import java.math.BigDecimal;import java.util.Scanner;public class Main{ public static void main(String[]args){ Scanner reader=new Scanner(System.in); while(reader.hasNext()){ _java 递归例题

面向对象程序设计(荣誉)实验一 String_对存储在string数组内的所有以字符‘a’开始并以字符‘e’结尾的单词做加密处理。-程序员宅基地

文章浏览阅读1.5k次,点赞6次,收藏6次。目录1.串应用- 计算一个串的最长的真前后缀题目描述输入输出样例输入样例输出题解2.字符串替换(string)题目描述输入输出样例输入样例输出题解3.可重叠子串 (Ver. I)题目描述输入输出样例输入样例输出题解4.字符串操作(string)题目描述输入输出样例输入样例输出题解1.串应用- 计算一个串的最长的真前后缀题目描述给定一个串,如ABCDAB,则ABCDAB的真前缀有:{ A, AB,ABC, ABCD, ABCDA }ABCDAB的真后缀有:{ B, AB,DAB, CDAB, BCDAB_对存储在string数组内的所有以字符‘a’开始并以字符‘e’结尾的单词做加密处理。

算法设计与问题求解/西安交通大学本科课程MOOC/C_算法设计与问题求解西安交通大学-程序员宅基地

文章浏览阅读68次。西安交通大学/算法设计与问题求解/树与二叉树/MOOC_算法设计与问题求解西安交通大学

随便推点

[Vue warn]: Computed property “totalPrice“ was assigned to but it has no setter._computed property "totalprice" was assigned to but-程序员宅基地

文章浏览阅读1.6k次。问题:在Vue项目中出现如下错误提示:[Vue warn]: Computed property "totalPrice" was assigned to but it has no setter. (found in <Anonymous>)代码:<input v-model="totalPrice"/>原因:v-model命令,因Vue 的双向数据绑定原理 , 会自动操作 totalPrice, 对其进行set 操作而 totalPrice 作为计..._computed property "totalprice" was assigned to but it has no setter.

basic1003-我要通过!13行搞定:也许是全网最奇葩解法_basic 1003 case 1-程序员宅基地

文章浏览阅读60次。十分暴力而简洁的解决方式:读取P和T的位置并自动生成唯一正确答案,将题给测点与之对比,不一样就给我爬!_basic 1003 case 1

服务器浏览war文件,详解将Web项目War包部署到Tomcat服务器基本步骤-程序员宅基地

文章浏览阅读422次。原标题:详解将Web项目War包部署到Tomcat服务器基本步骤详解将Web项目War包部署到Tomcat服务器基本步骤1 War包War包一般是在进行Web开发时,通常是一个网站Project下的所有源码的集合,里面包含前台HTML/CSS/JS的代码,也包含Java的代码。当开发人员在自己的开发机器上调试所有代码并通过后,为了交给测试人员测试和未来进行产品发布,都需要将开发人员的源码打包成Wa..._/opt/bosssoft/war/medical-web.war/web-inf/web.xml of module medical-web.war.

python组成三位无重复数字_python组合无重复三位数的实例-程序员宅基地

文章浏览阅读3k次,点赞3次,收藏13次。# -*- coding: utf-8 -*-# 简述:这里有四个数字,分别是:1、2、3、4#提问:能组成多少个互不相同且无重复数字的三位数?各是多少?def f(n):list=[]count=0for i in range(1,n+1):for j in range(1, n+1):for k in range(1, n+1):if i!=j and j!=k and i!=k:list.a..._python求从0到9任意组合成三位数数字不能重复并输出

ElementUl中的el-table怎样吧0和1改变为男和女_elementui table 性别-程序员宅基地

文章浏览阅读1k次,点赞3次,收藏2次。<el-table-column prop="studentSex" label="性别" :formatter="sex"></el-table-column>然后就在vue的methods中写方法就OK了methods: { sex(row,index){ if(row.studentSex == 1){ return '男'; }else{ return '女'; }..._elementui table 性别

java文件操作之移动文件到指定的目录_java中怎么将pro.txt移动到design_mode_code根目录下-程序员宅基地

文章浏览阅读1.1k次。java文件操作之移动文件到指定的目录_java中怎么将pro.txt移动到design_mode_code根目录下

推荐文章

热门文章

相关标签