Android音视频开发入门(5)使用LAME编码一个PCM文件,为了跳槽强刷1000道Android真题
无论是自行安装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的源码就已经添加到我们的项目中了。但是因为文件里面一些引入的路径已经变了,所以我们要对这些引入的路径进行更改:
删除
fft.c
文件的 47 行的 include“vector/lame_intrin.h”删除掉
set_get.h
的第24行修改
util.h
文件的 570 行的extern ieee754_float32_t fast_log2(ieee754_float32_t x)
为extern float fast_log2(float x)
此时还有很多文件报错,因为没有定义宏 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.h
和 mp3_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真题相关推荐
- 为了跳槽强刷1000道Android真题,面试资料分享
前言 近日,字节跳动正式启动了2021届秋季校园招聘,为应届毕业生开放超过6000个工作岗位.这一数字超过了该公司往年秋招规模,并与其今年春招规模持平.全年校招人数共计超过1万2千人,远高于同类型互联 ...
- 安卓音视频开发!为了跳槽强刷1000道Android真题,大厂直通车!
为什么要做职业规划? 我们先聊聊第一个话题,为什么要做职业规划? 首先,我们要知道职业规划是什么,也就是如何持续选择适合自己发展的工作的过程. 职业规划其实就是对职业生涯乃至人生进行持续的.系统的.计 ...
- Android 音视频开发(二):使用 AudioRecord 采集音频PCM并保存到文件(学习笔记)
关于 AudioRecord Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风 ...
- Android 音视频开发入门指南
最近收到很多网友通过邮件或者留言说想学习音视频开发,该如何入门,我今天专门写篇文章统一回复下吧. 音视频这块,目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的,希望我后面能挤出时间整 ...
- Android音视频开发入门指南
<Android 音视频从入门到提高 -- 任务列表> 1. 在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView,SurfaceView,自定义 Vi ...
- Android 音视频开发学习思路
Android 音视频开发这块目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的.只能通过一点点的学习和积累把这块的知识串联积累起来. 初级入门篇: Android 音视频开发(一) ...
- android音频开发6,Android 音视频开发(一) : 通过三种方式绘制图片
想要逐步入门音视频开发,就需要一步步的去学习整理,并积累.本文是音视频开发积累的第一篇. 对应的要学习的内容是:在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView ...
- 23最新《Android音视频开发进阶指南》,音视频开发者速领
作为Android开发程序员,我们时刻站在互联网的前端,而音视频作为现在乃至未来几年一个强劲的风口,吸引了许多程序员的关注. 那么音视频开发的行业现状究竟如何呢?我们又该怎样入门呢?请看下文: 音视频 ...
- Android 音视频开发(一) -- 使用AudioRecord 录制PCM(录音);AudioTrack播放音频
前言,音视频这块,确实比较难入门,本着学习的态度,我这边也跟着 Android 音视频开发入门指南 打怪升级,留下个脚印,大家共勉. 音视频 系列文章 Android 音视频开发(一) – 使用Aud ...
最新文章
- QT 防止FTP 上传软件在断连处 Crash
- 笔记本电脑有蓝牙连接功能吗_百元蓝牙无线键盘推荐——罗技K380
- 多项式对数函数ln f(x)
- java观察者模式在spring中的应用_在Spring中使用观察者模式
- 让oracle跑得更快——oracle 10g性能分析与优化思路,[让Oracle跑得更快.Oracle.10g性能分析与优化思路]概要1.doc...
- php环形链表,PHP环形链表实现方法示例
- linux进程控制(一)--unix环境高级编程读书笔记
- Log4j框架配置文件log4j.properties配置使用详解
- 计算机显微视觉相关概念,计算机视觉热门科研!基于深度神经网络的蛋白质智能显微分类系统,已开启!...
- 论文的重复率修改方法
- GD32VF103启动流程分析
- JUnit 单元测试
- 操作无法完成 因为文件已在system中打开
- 如何选择适合的大数据分析软件
- Android欢迎页面以及引导页面
- perf常用命令(perf top perf record perf stat)
- 工作习惯决定事业成败
- matplotlib 绘制三角函数图像
- 解决必应biying搜索跳转到百度www.baidu.com搜索的解决方法
- matlab/simulink学习的笔记都总结在这里
热门文章
- Debian改变网卡名称
- [DX10游戏教程(C++)]教程1:在Visual Studio 2012中配置DirectX 10
- 贝叶斯分析助你成为优秀的调参侠:自动化搜索物理模型的参数空间
- int正数和负数的原码、反码、补码
- js 伪造referer_javascript操作referer详细解析
- 深入理解计算机系统--链接
- spring中自定义注解(annotation)与AOP中获取注解___使用aspectj的@Around注解实现用户操作和操作结果日志
- Oracle性能优化概述
- 37.深度解密三十七:网络营销推广之百度经验营销全过程步骤讲解
- 听音乐用什么蓝牙耳机好?音质好的tws蓝牙耳机推荐