Android音视频开发入门(5)使用LAME编码一个PCM文件,Activity的6大难点你会几个

我们在java的路径下,通过右键->new->Folder->JniFolder 建立一个JNI文件,然后把这个头文件拖入到jni目录下。而之前那个.class文件就可以删除掉了

在这里插入图片描述

接下来我们在jni下新建一个cpp文件 Mp3Encoder,将刚刚生成文件的内容全部复制到这个文件下面,并且实现对应的方法,这里就是做一个日志打印.:

//

// Created by msn on 2019/11/17.

//

#include <jni.h>

#include <android/log.h>

#define LOG_TAG “Mp3Encoder”

#ifndef _Included_com_rikkatheworld_mp3encoder_studio_Mp3Encoder

#define _Included_com_rikkatheworld_mp3encoder_studio_Mp3Encoder

//宏定义 一个日志打印

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

#ifdef __cplusplus

extern “C” {

#endif

JNIEXPORT void JNICALL Java_com_rikkatheworld_mp3encoder_studio_Mp3Encoder_encoder

(JNIEnv *, jobject) {

//这里就做一个打印日志

LOGI(“encoder encode”);

}

#ifdef __cplusplus

}

#endif

#endif

接着在 app build.gradle下加入:

android {

defaultConfig {

externalNativeBuild {

cmake {

//设置C++标准为默认

cppFlags “”

}

}

ndk {

//ndk模块名称

moduleName “mp3_encoder”

//声明log

ldLibs “log”

}

}

externalNativeBuild {

cmake {

//指定CMake文件路径

path “CMakeLists.txt”

}

}

}

接着我们在 app下面创建 CMakeLists.txt,(以前是使用Android.mk,这个相当于代替它的)

For more information about using CMake with Android Studio, read the

documentation: https://d.android.com/studio/projects/add-native-code.html

Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

Creates and names a library, sets it as either STATIC

or SHARED, and provides the relative paths to its source code.

You can define multiple libraries, and CMake builds them for you.

Gradle automatically packages shared libraries with your APK.

add_library( # 输入ndk使用的名称

mp3_encoder

Sets the library as a shared library.

SHARED

输入你的cpp的路径名.

src/main/jni/Mp3Encoder.cpp )

Searches for a specified prebuilt library and stores the path as a

variable. Because CMake includes system libraries in the search path by

default, you only need to specify the name of the public NDK library

you want to add. CMake verifies that the library exists before

completing its build.

find_library( # Sets the name of the path variable.

log-lib

Specifies the name of the NDK library that

you want CMake to locate.

log)

Specifies libraries CMake should link to your target library. You

can link multiple libraries, such as libraries you define in this

build script, prebuilt third-party libraries, or system libraries.

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

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

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

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

img

img

img

img

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

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

]

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

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

[外链图片转存中…(img-k1yHMgjd-1712447974752)]

[外链图片转存中…(img-vnbhCGlI-1712447974752)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值