## 实战演练
* 黑马秀秀(应用运行和.C的源文件无关,主要是.so这个动态链接库)
* 压力表案例
* CPP开发JNI(不同于C开发JNI)
* fork子线程(fork出一个C的子进程,完成java无法完成的功能)
## c++ 开发JNI
* C的预处理命令
* #开头的就是c/c++的预处理命令
* 在编译之前 先会走预编译阶段 预编译阶段的作用就是 把 include进来的头文件 copy到源文件中
* define这些宏定义 用真实的值替换一下
* #if #else #endif 该删除的删除掉
*
* c++开发jni代码时 env不再是结构体Jninativeinterface的二级指针
* _JNIEnv JNIEnv _JNIEnv 是C++的结构体; C++结构体跟C区别 :C++的结构体可以定义函数,C语言中结构体中不能定义函数,可以定义函数指针
* env 是JNIEnv的一级指针 也就是结构体_JNIEnv的一级指针 env-> 来调用 结构体里的函数
* _JNIEnv的函数 实际上调用的就是结构体JNINativeInterface的同名函数指针,因为_JNIEnv结构体中定义了一个Jninativeinterface同名函数指针
* 在调用时第一个参数 env已经传进去了
* C++的函数要先声明再使用 可以把javah生成的头文件include进来作为函数的声明,即预处理#include来加载头文件如#include "com_wsc_cppJNI_MainActivity.h"
* include的方法 <> "" ""
* 如果用"" 来导入头文件 系统会先到 源代码所在的文件夹去找头文件 如果找不到再到系统指定的incude文件夹下找
* //用<> 直接到系统指定的include目录下去找
* javah生成的.h头文件要复制到jni文件夹下面
### JNI开发之fork分叉出一个进程 ###
**java中代码**
package com.wsc.cforkdemo;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
/**
* java进程产生一个C的进程,再通过C的进程把java的进程跑起来 通过调用本地函数cfork,fork分叉出一个进程
*
* @author wangsc
*
*/
public class MainActivity extends Activity {
static {
System.loadLibrary("cfork");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void fork(View v) {
cfork();
}
public native void cfork();
}
**C中代码 cfork.c中的代码**
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/*
* Class: com_wsc_cforkdemo_MainActivity
* Method:cfork
* Signature: ()V
*/JNIEXPORT void JNICALL Java_com_wsc_cforkdemo_MainActivity_cfork(JNIEnv * env,
jobject obj) {
int pid = fork();
//fork成功的分叉出一个子进程,会返回当前进程的id,但是只能在主进程中fork成功,这个子进程是C的进程,包含了父进程的资源
//主进程运行完,fork子进程又会运行一遍C中的代码,fork子进程中运行fork会返回0但是不能再分叉出新的进程,所以看到logcat,打印出pid有2个数值
//fork的返回值可能三种 >0 ==0 <0
if (pid > 0) {
LOGD("pid= %d", pid);//主进程中执行的代码
} else if (pid == 0) {
LOGD("pid == 0");//子进程运行的代码
} else {
LOGD("pid < 0 ");
}
}
运行效果如下图所示
打印logcat如下图
![](http://i.imgur.com/YJk0pVI.png)
![](http://i.imgur.com/M0zklSR.png)
通过adb shell命令可以查看手机中运行的进程
![](http://i.imgur.com/RPLM8bV.png)
![](http://i.imgur.com/8av90m5.png)
## am 命令
* am命令 :在adb shell里可以通过am命令进行一些操作 如启动activity Service 启动浏览器等等
* am命令的源码在Am.java中, 在adb shell里执行am命令实际上就是启动一个线程执Am.java的main方法,am命令后面带的参数都会当作运行时的参数传递到main函数中
* am命令可以用start子命令,并且带指定的参数
* 常见参数: -a: action -d data -t 表示传入的类型 -n 指定的组件名字
* 举例: 在adb shell中通过am命令打开网页
* am start --user 0 -a android.intent.action.VIEW -d http://www.baidu.com
* 通过am命令打开activity
* am start --user 0 -n com.itheima.fork/com.itheima.fork.MainActivity
* (系统sdk版本>16 需要加上--user 0 , <16不需要加)
* execlp c语言中执行系统命令的函数(linux系统下execlp命令)
* execlp() 会从PATH环境变量所指的目录中查找符合参数file的文件找到后就执行该文件, 第二个参数开始就是执行这个文件的 args[0],args[1] 最后一个参数用(char*)NULL结束
* android开发中 execlp函数对应android的path路径为system/bin/目录
* 调用格式
execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *) NULL);
execlp("am", "am", "start", "--user","0", "-n" , "com.itheima.cforktest/com.itheima.cforktest.MainActivity",(char *) NULL);
* 这样就会起到上面在命令行窗口使用adb shell 使用am命令同等的效果
**注意:通过fork子进程拿到一个C的进程,实际上C的进程,通过android提供的API拿到了java进程的列表,因此可把各种java进程(获取到权限的)都干掉,但是C的进程通过android的API无法获取到,所以说分叉出子进程有时很重要,比如推送进程(商城/资讯/即时通信等)。比如即时通信类,后台肯定保持一个长链接,后台会有一个service,service和服务器维持着一个长链接;如何保证长链接不被干掉,在service启动后,通过service再fork出一个子进程;如果发现service不在了,首先查看是不是应用被卸载了,如果被卸载了,就通过浏览器弹出一个调查的页面;如果应用还在,就startservice,传一个startservice;注意,如果进程被kill掉,service不会走生命周期的;使用java的方法不能保证service存在,service一旦挂掉,应用将收不到任何的消息,直到重新打开应用。这时可以fork出子进程,监听父进程的id。**
### MP3转码器 ###
* 在电脑里面所有数据都是以0011形式存储,windows下 文件类型的区别是 文件的扩展名
* .jpg
* .bmp
* .gif
* .mp3
* .wav
* .mp4
* .rmvb
* .txt
* 由于扩展名可以被更改,所以需要第二道防线就是文件文件头
* 1.读文件头( 记录文件的大小 ,文件格式, 文件的编码方式);
* 2.不同格式的文件如何存储
* jpg 图形规范: 图片压缩
* bmp 位图
* 声音
* wav 无损的音频格式 文件比较大
* mic 硬件设备 -产生电流 -> pcm 音频裸数据 ->wav 的文件
* mp3 ->声音的压缩算法. winrar 7-zip 000000000000 -> 1万个0
* mp3文件头 每一帧大小为 crc32 校验
* 视频
* 声音 图片
* mpeg mp4 3gp
* rm
* rmvb
* jave java audio wideo encoder 开源的音频代码库,不可跨平台,只能在windows平台使用
* android录音机只能录制pcm格式、amr格式的数据的数据
* pcm、wav--->mp3
* java只有jave这个开源的音频代码库,所以需要寻找C的代码库实现音频的转码,比如lame,开源高效的MP3的编码器
### c语言文件操作模式 ###
* “rt” 只读打开一个文本文件,只允许读数据
* “wt” 只写打开或建立一个文本文件,只允许写数据
* “at” 追加打开一个文本文件,并在文件末尾写数据
* “rb” 只读打开一个二进制文件,只允许读数据
* “wb” 只写打开或建立一个二进制文件,只允许写数据
* “ab” 追加打开一个二进制文件,并在文件末尾写数据
* “rt+” 读写打开一个文本文件,允许读和写
* “wt+” 读写打开或建立一个文本文件,允许读写
* “at+” 读写打开一个文本文件,允许读,或在文件末追加数据
* “rb+” 读写打开一个二进制文件,允许读和写
* “wb+” 读写打开或建立一个二进制文件,允许读和写
“ab+” 读写打开一个二进制文件,允许读,或在文件末追加数据
* 对于文件使用方式有以下几点说明:
* 文件使用方式由r,w,a,t,b,+六个字符拼成,各字符的含义是:
* r(read): 读
* w(write): 写
* a(append): 追加
* t(text): 文本文件,可省略不写
* b(banary): 二进制文件
### MP3转码器的UI界面 ###
![](http://i.imgur.com/rkQ7Es1.png)
### 音频压缩的两部分 ###
* 音频压缩实际上包含两个部分。 第一部分被称之为编码,这是一个将数字音频数据(通常为WAVE文件)转为被称为比特流(bitstream)的高压缩形式。
* 如果你要在声卡上播放这种比特流,你需要进行另一部分操作——被称为解码 解码将比特流重新放大还原成WAVE文件。
* 实现第一部分效果的程序被称为编码器。 LAME就是这样一个编码器。
* 实现第二部分效果的程序被称为解码器。 Xmms就是一个著名的MPEG3解码器,另外还有 mpg123。 你可以在www.mp3-tech.org 上找到它们。
一般移植了某一个C代码,测试C代码是否正常运行,需要获取其版本号,以string方式返回。
### 固定比特率/平均比特率/变动比特率 3种编码模式 ###
* 固定比特率(CBR)
* 这是一种固定编码模式,也是最基本的模式 在这种模式中,比特率在整个文件中保持一致。 这就意味着你的mp3文件中的每一部分将在压缩是使用相同的位数。 编码一段复杂的音乐片段或是简单的音乐片段的时候,编码器将使用相同的比特率,所以这段mp3的音质是变动的。 复杂部分的音质将低于简单部分的。 这种模式的最大优势在于文件最终的大小不会变动而且可以精确计算出。
* 平均比特率(ABR)
* 在这种模式下,你可以制定一种预定的比特率,编码器将试着不断维持这种平均比特率同时在你的音乐的某些片断需要更高数位压缩的时候使用较高的比特率。 这种编码的音质将比CBR编码好,而且最终文件的平均大小仍然可以预测,因此比起CBR模式,我们高度推荐这种编码模式。
* 变动比特率(VBR)
* 在这种模式中,你可以在0(高音质/低变形)至9(低音质/高变形)之间制定你希望的音质效果。 编码器在压缩你的音乐时选择最佳的比特数对应于其每一部分,从而尽可能维持整个文件符合说给定的音质。 这种编码模式的最大优点在于你能够指定你所希望达到的音质等级,但是问题在于这样将使最终的文件大小完全不可预知。
**打印LOG日志比较消耗性能,实际开发中应该注释掉**
### 绕过JNI调用C代码
在普通android 开发里面用的不多,但是在自己定制的平板、机顶盒上会有广泛的应用,在android系统下的手机病毒或者黑客程序中也应用广泛,这是中中国人发现的方法。
#### 纯c语言开发程序
> 需要在windows平台上编译出一个arm平台下可以运行的代码,这个交叉编译不需要NDK了,需要用到Sourcery G++ Lite Edition for ARM.
* 1.下载编译器和链接器软件.Sourcery G++ Lite Edition for ARM.
* arm-none-linux-gnueabi-gcc.exe是编译命令
* bin/arm-none-linux-gnueabi-ld.exe是链接命令
* 2.编写c源文件
#include <stdio.h>
int main()
{
printf("Hello, Android!\n");
return 0;
}
* 3.编译hello.c源文件
* 进入cmd
* 执行 arm-none-linux-gnueabi-gcc HelloWorld.c -static -o hellostatic
* 4.将hellostatic文件传输手机
* adb push hellostatic /data/c/
* 5.改变文件的授权
* adb shell chmod 777 /data/c/hellostatic
* 6.运行程序
* adb shell
* cd /data/c
* ./hellostatic
* 7.查看执行结果
- 前言
- JNI基础知识
- C语言知识点总结
- ①基本语法
- ②数据类型
- 枚举类型
- 自定义类型(类型定义)
- ③格式化输入输出
- printf函数
- scanf函数
- 编程规范
- ④变量和常量
- 局部变量和外部变量
- ⑤类型转换
- ⑥运算符
- ⑦结构语句
- 1、分支结构(选择语句)
- 2、循环结构
- 退出循环
- break语句
- continue语句
- goto语句
- ⑧函数
- 函数的定义和调用
- 参数
- 函数的返回值
- 递归函数
- 零起点学通C语言摘要
- 内部函数和外部函数
- 变量存储类别
- ⑨数组
- 指针
- 结构体
- 联合体(共用体)
- 预处理器
- 预处理器的工作原理
- 预处理指令
- 宏定义
- 简单的宏
- 带参数的宏
- 预定义宏
- 文件包含
- 条件编译
- 内存中的数据
- C语言读文件和写文件
- JNI知识点总结
- 前情回顾
- JNI规范
- jni开发
- jni开发中常见的错误
- JNI实战演练
- C++(CPP)在Android开发中的应用
- 掘金网友总结的音视频开发知识
- 音视频学习一、C 语言入门
- 1.程序结构
- 2. 基本语法
- 3. 数据类型
- 4. 变量
- 5. 常量
- 6. 存储类型关键字
- 7. 运算符
- 8. 判断
- 9. 循环
- 10. 函数
- 11. 作用域规则
- 12. 数组
- 13. 枚举
- 14. 指针
- 15. 函数指针与回调函数
- 16. 字符串
- 17. 结构体
- 18. 共用体
- 19. typedef
- 20. 输入 & 输出
- 21.文件读写
- 22. 预处理器
- 23.头文件
- 24. 强制类型转换
- 25. 错误处理
- 26. 递归
- 27. 可变参数
- 28. 内存管理
- 29. 命令行参数
- 总结
- 音视频学习二 、C++ 语言入门
- 1. 基本语法
- 2. C++ 关键字
- 3. 数据类型
- 4. 变量类型
- 5. 变量作用域
- 6. 常量
- 7. 修饰符类型
- 8. 存储类
- 9. 运算符
- 10. 循环
- 11. 判断
- 12. 函数
- 13. 数学运算
- 14. 数组
- 15. 字符串
- 16. 指针
- 17. 引用
- 18. 日期 & 时间
- 19. 输入输出
- 20. 数据结构
- 21. 类 & 对象
- 22. 继承
- 23. 重载运算符和重载函数
- 24. 多态
- 25. 数据封装
- 26. 接口(抽象类)
- 27. 文件和流
- 28. 异常处理
- 29. 动态内存
- 30. 命名空间
- 31. 预处理器
- 32. 多线程
- 总结
- 音视频学习 (三) JNI 从入门到掌握
- 音视频学习 (四) 交叉编译动态库、静态库的入门学习
- 音视频学习 (五) Shell 脚本入门
- 音视频学习 (六) 一键编译 32/64 位 FFmpeg 4.2.2
- 音视频学习 (七) 掌握音频基础知识并使用 AudioTrack、OpenSL ES 渲染 PCM 数据
- 音视频学习 (八) 掌握视频基础知识并使用 OpenGL ES 2.0 渲染 YUV 数据
- 音视频学习 (九) 从 0 ~ 1 开发一款 Android 端播放器(支持多协议网络拉流/本地文件)
- 音视频学习 (十) 基于 Nginx 搭建(rtmp、http)直播服务器
- 音视频学习 (十一) Android 端实现 rtmp 推流
- 音视频学习 (十二) 基于 FFmpeg + OpenSLES 实现音频万能播放器
- 音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源)