技术标签: 2024年程序员学习 音视频 android pcm
target_link_libraries( # 输入你的ndk模块名
mp3_encoder
${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项目的构建啦~
文件目录如下:
===============================================================================
交叉编译是音视频开发中必需的,因为无论在哪个移动平台下开发,第三方库都是需要进行交叉编译的。
本节会从交叉编译的原理开始介绍,然后会在两个移动平台下编译出音视频开发常用的几个库,包括 X264、 FDK_AAC、LAME,最终将以LAME库为例进行实践,完成一个将音视频的PCM裸数据编码成MP3文件的实例,以此来证明交叉编译的重要性。
所以交叉编译,就是 在一个平台(PC)上生成另外一个平台(Android、IOS)的可执行代码。
Q:Android为什么要进行交叉编译呢?
A:即使是Android设备具有越来越强的计算能力,但是有两个原因不能在 这种嵌入式设备上进行本地编译:
还是计算能力的问题,不够全面,不够极致
ARM平台上没有较好的编译环境,这导致整个编译过程异常繁琐
所以大部分的嵌入式开发平台都是提供了 本身平台交叉编译所需要的交叉工具编译链(Android提供了 Eclipse SDK、Android Studio编译器),这样开发者就能在 PC上编译出可以运行在ARM平台下的程序了。
无论是自行安装PC上的编译器,还是下载其他平台的交叉编译链,它们都会提供下面几个工具:
编译器,对C源文件进行编译处理,生成汇编文件
将汇编文件生成目标文件
打包器,用于库操作
链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者是可执行文件
调试工具
最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码
查看静态库文件中的符号表
查看静态库或者动态库中的方法名
在编译之前,我们先看看LAME、FDK_ACC等这些的概念简介:
是目前非常优秀的一种MP3编译引擎,在业界,转码成 MP3格式的音频文件时,最常用的编码器就是LAME库。当达到320Kbits/s以上时,LAME编码出来的音频质量几乎可以CD的音质相媲美。并且保证整个音频文件的体积非常小。
因此若要在移动平台上编码 MP3文件,使用LAME便成为唯一选择。
FDK_ACC 是用来编码和解码的AAC格式音频文件的开源库。
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库
先去 传送门 下载好LAME的源码然后解压缩。
解压完后将 libmp3lame
文件夹下的所有的 带 .h
和带 .c
的 C/C++文件 和 include
下的lame.h
复制到 JNI目录下(最好再统一放到一个新的子目录下,这边就放到了 lame子目录下),因为添加了这么多的文件,那么需要把这些文件写入到CMake的 add_library
:
add_library( # Sets the name of the library.
mp3_encoder
SHARED
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”
/**
*/
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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。
技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到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 小时用来学习。
不出半年,你就能看出变化!
文章浏览阅读3.8k次。1、将下载好的萤石js插件,添加到SoringBoot项目中。位置可参考下图所示。(容易出错的地方,在将js插件在html页面引入时,发生路径错误的问题)所以如果对页面中引入js的路径不清楚,可参考下图所示存放路径。2、将ezuikit.js引入到demo-live.html中。(可直接将如下代码复制到你创建的html页面中)<!DOCTYPE html><html lan..._ezuikit 测试的url
文章浏览阅读322次。第二步,在弹出的对话框选择,设备驱动—>PLC—>莫迪康—>ModbusRTU—>COM,根据配置软件选择的协议选期期,这里以此为例,然后点击“下一步”。第四步,把使用虚拟串口打勾(GPRS设备),根据需要选择要生成虚拟口,这里以选择KVCOM1为例,然后点击“下一步”设备ID即Modbus地址(1-255) 使用DTU时,为下485接口上的设备地址。第六步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“第五步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“_组态王ua
文章浏览阅读9.4k次,点赞22次,收藏19次。安装npm相当于安装node.js,Node.js已自带npm,安装Node.js时会一起安装,npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西_npm安装配置
文章浏览阅读748次,点赞21次,收藏26次。大家好,小编来为大家解答以下问题,python基础训练100题,python入门100例题,现在让我们一起来看看吧!宝子们还在新手村练级的时候,不单要吸入基础知识,夯实自己的理论基础,还要去实际操作练练手啊!由于文章篇幅限制,不可能将100道题全部呈现在此除了这些,下面还有我整理好的基础入门学习资料,视频和讲解文案都很齐全,用来入门绝对靠谱,需要的自提。保证100%免费这不,贴心的我爆肝给大家整理了这份今天给大家分享100道Python练习题。大家一定要给我三连啊~
文章浏览阅读1k次。 为了在 Linux ( Ubuntu) 上安装sublime,一般大家都会选择常见的教程或是 sublime 官网教程,然而在国内这种方法可能失效。为此,需要用安装包安装。以下就是使用官网安装包安装的教程。打开 sublime 官网后,点击右上角 download, 或是直接访问点击打开链接,即可看到各个平台上的安装包。选择 Linux 64 位版并下载。下载后,打开终端,进入安装..._ubuntu 安装sumlime text打不开
文章浏览阅读563次,点赞13次,收藏6次。CrossOver24是一款类虚拟机软件,专为macOS和Linux用户设计。它的核心技术是Wine,这是一种在Linux和macOS等非Windows操作系统上运行Windows应用程序的开源软件。通过CrossOver24,用户可以在不购买Windows授权或使用传统虚拟机的情况下,直接在Mac或Linux系统上运行Windows软件和游戏。该软件还提供了丰富的功能,如自动配置、无缝集成和实时传输等,以实现高效的跨平台操作体验。
文章浏览阅读1.7k次。一个用聊天的方式让ChatGPT帮我写的线程安全的环形List_为什么gpt一写list就卡
文章浏览阅读336次。我们在前面的文章里曾写过Web应用中乱码产生的原因和处理方式,旧文回顾:深度揭秘乱码问题背后的原因及解决方式其中我们提到可以通过Filter的方式来设置请求和响应的encoding,来解..._filterconfig selectencoding
文章浏览阅读651次。转自:http://www.jb51.net/article/36480.htmencodeURI和decodeURI是成对来使用的,因为浏览器的地址栏有中文字符的话,可以会出现不可预期的错误,所以可以encodeURI把非英文字符转化为英文编码,decodeURI可以用来把字符还原回来_js encodeur decodeurl
文章浏览阅读1.9w次,点赞6次,收藏3次。前言在日常的Android开发当中,我们肯定要打包apk。但是今天我打包的时候遇到一个很奇怪的问题Android The destination folder does not exist or is not writeable,大意是目标文件夹不存在或不可写。出现问题的原因以及解决办法上面有说报错的中文大意是:目标文件夹不存在或不可写。其实问题就在我们的打包界面当中图中标红的Desti..._the destination folder does not exist or is not writeable
文章浏览阅读94次。一、配置代码编辑区的样式 <1>打开Eclipse,Help —> Install NewSoftware,界面如下: <2>点击add...,按下图所示操作: name:随意填写,Location:http://eclipse-color-th..._ecplise高大上设置
文章浏览阅读2.8k次。一,下载mysql:http://dev.mysql.com/downloads/mysql/; 打开页面之后,在Select Platform:下选择linux Generic,如果没有出现Linux的选项,请换一个浏览器试试。我用的谷歌版本不可以,换一个别的浏览器就行了,如果还是不行,需要换一个翻墙的浏览器。 二,下载完后解压缩并放到安装文件夹下: 1、MySQL-client-5.6.2_linux mysql 安装 mysql-5.6.24-1.linux_glibc2.5.x86_64.rpm-bundle