1、AndroidManifest.xml
D:\WorkSpace\Android\AndroidLearn\GitHubRepo\ndk-samples\camera\basic\src\main\AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0">
<uses-feature android:name="android.hardware.camera" />重点:需要的camera此硬件的功能
<uses-permission android:name="android.permission.CAMERA" />重点:需要的camera此硬件的运行时权限
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:screenOrientation="sensorLandscape"
android:configChanges="keyboardHidden|orientation|screenSize"重点:触发 onConfirationChanged
android:hasCode="true">
<activity android:name="com.sample.camera.basic.CameraActivity"
android:label="@string/app_name"
android:exported="true">
<meta-data android:name="android.app.lib_name"
android:value="ndk_camera" />重点:ndk_camera
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
2、CameraActivity.java
D:\WorkSpace\Android\AndroidLearn\GitHubRepo\ndk-samples\camera\basic\src\main\java\com\sample\camera\basic\CameraActivity.java
class CameraSeekBar {
int _progress;
long _min, _max, _absVal;
SeekBar _seekBar;
TextView _sliderPrompt;
CameraSeekBar() {
_progress = 0;
_min = _max = _absVal = 0;
}
CameraSeekBar(SeekBar seekBar, TextView textView, long min, long max, long val) {
_seekBar = seekBar;//重点:CameraSeekBar构建函数
_sliderPrompt = textView;
_min = min;
_max = max;
_absVal = val;
if(_min != _max) {
_progress = (int) ((_absVal - _min) * _seekBar.getMax() / (_max - _min));
seekBar.setProgress(_progress);
updateProgress(_progress);//重点:更新进度条
} else {
_progress = 0;
seekBar.setEnabled(false);
}
}
public boolean isSupported() {
return (_min != _max);
}
public void updateProgress(int progress) {//重点:更新进度条
if (!isSupported())
return;
_progress = progress;
_absVal = (progress * ( _max - _min )) / _seekBar.getMax() + _min;
int val = (progress * (_seekBar.getWidth() - 2 * _seekBar.getThumbOffset())) / _seekBar.getMax();
_sliderPrompt.setText("" + _absVal);//重点,设定进度条值的位置
_sliderPrompt.setX(_seekBar.getX() + val + _seekBar.getThumbOffset() / 2);
}
public int getProgress() {
return _progress;
}
public void updateAbsProgress(long val) {//重点:更新绝对值的进度条
if (!isSupported())
return;
int progress = (int)((val - _min) * _seekBar.getMax() / (_max - _min));
updateProgress(progress);
}
public long getAbsProgress() {//重点:获取进度条的对应的绝对值
return _absVal;
}
}
public class CameraActivity extends NativeActivity
implements ActivityCompat.OnRequestPermissionsResultCallback {
volatile CameraActivity _savedInstance;
PopupWindow _popupWindow;
ImageButton _takePhoto;
CameraSeekBar _exposure, _sensitivity;
long[] _initParams;//重点,存放摄像头的曝光值,灵敏度值
private final String DBG_TAG = "NDK-CAMERA-BASIC";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(DBG_TAG, "OnCreate()");
// new initialization here... request for permission
_savedInstance = this;
setImmersiveSticky();
View decorView = getWindow().getDecorView();//重点,view层级的根View为 decorView
decorView.setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
setImmersiveSticky();
}
});
}
private boolean isCamera2Device() {//重点,通过hardware下的JAVA工具类,检测camera是否存在
CameraManager camMgr = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
boolean camera2Dev = true;
try {
String[] cameraIds = camMgr.getCameraIdList();
if (cameraIds.length != 0 ) {
for (String id : cameraIds) {
CameraCharacteristics characteristics = camMgr.getCameraCharacteristics(id);
int deviceLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
int facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (deviceLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY &&
facing == LENS_FACING_BACK) {
camera2Dev = false;
}
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
camera2Dev = false;
}
return camera2Dev;
}
// get current rotation method
int getRotationDegree() {//重点,获取当前G-Sensor旋转的角度
return 90 * ((WindowManager)(getSystemService(WINDOW_SERVICE)))
.getDefaultDisplay()
.getRotation();
}
@Override
protected void onResume() {
super.onResume();
setImmersiveSticky();
}
void setImmersiveSticky() {
View decorView = getWindow().getDecorView();//重点,view层级的根View为 decorView
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
@Override
protected void onPause() {
if (_popupWindow != null && _popupWindow.isShowing()) {
_popupWindow.dismiss();//重点,camera应用退到后台时候,_popupWindow消失
_popupWindow = null;
}
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
private static final int PERMISSION_REQUEST_CODE_CAMERA = 1;
public void RequestCamera() {
if(!isCamera2Device()) {
Log.e(DBG_TAG, "Found legacy camera Device, this sample needs camera2 device");
return;
}
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.CAMERA},
PERMISSION_REQUEST_CODE_CAMERA
);//重点,请求摄像头的运行时权限组
return;
}
notifyCameraPermission(true);//JAVA侧通知C++侧,运行时权限授权是否成功,第一次通知成功
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode != PERMISSION_REQUEST_CODE_CAMERA) {//配套的请求运行时权限的CODE值
// The permissions request isn't ours.
super.onRequestPermissionsResult(requestCode,
permissions,
grantResults);
return;
}
if (permissions.length == 0) {
// https://developer.android.com/reference/androidx/core/app/ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int,java.lang.String[],int[])
//
// Note: It is possible that the permissions request interaction with the user is
// interrupted. In this case you will receive empty permissions and results arrays which
// should be treated as a cancellation.
//
// The docs aren't clear about *why* it might be canceled, so it's not clear what we
// should do here other than restart the request.
RequestCamera();//重点,再次请求摄像头的运行时权限
return;
}
boolean granted = Arrays.stream(grantResults)
.allMatch(element -> element == PackageManager.PERMISSION_GRANTED);
if (!granted) {
logDeniedPermissions(permissions, grantResults);//记录运行时权限组的授权情况
}
notifyCameraPermission(granted);//JAVA侧通知C++侧,运行时权限授权是否成功,二次确认地方
}
private void logDeniedPermissions(//记录运行时权限组的授权情况
@NonNull String[] requestedPermissions,
@NonNull int[] grantResults
) {
if (requestedPermissions.length != grantResults.length) {
throw new IllegalArgumentException(
String.format(
"requestedPermissions.length (%d) != grantResults.length (%d)",
requestedPermissions.length,
grantResults.length
)
);
}
for (int i = 0; i < requestedPermissions.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
Log.i(DBG_TAG, requestedPermissions[i] + " DENIED");
}
}
}
/**
* params[] exposure and sensitivity init values in (min, max, curVa) tuple
* 0: exposure min
* 1: exposure max
* 2: exposure val
* 3: sensitivity min
* 4: sensitivity max
* 5: sensitivity val
*/
@SuppressLint("InflateParams")//重点,从C++侧调用到JAVA层,刷新Camera的界面
public void EnableUI(final long[] params)
{//重点,params存放摄像头的曝光值,灵敏度值
// make our own copy
_initParams = new long[params.length];
System.arraycopy(params, 0, _initParams, 0, params.length);
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (_popupWindow != null) {
_popupWindow.dismiss();
}
LayoutInflater layoutInflater
= (LayoutInflater) getBaseContext()
.getSystemService(LAYOUT_INFLATER_SERVICE);
View popupView = layoutInflater.inflate(R.layout.widgets, null);//重点,通过视图的填充器,填入widgets视图
_popupWindow = new PopupWindow(//配置widgets视图的宽度、高度
popupView,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT);
RelativeLayout mainLayout = new RelativeLayout(_savedInstance);//mainLayout为父类布局,_savedInstance为CameraActivity这个Context
ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(
-1, -1);//mainLayout为父类布局的参数
params.setMargins(0, 0, 0, 0);
_savedInstance.setContentView(mainLayout, params);//设定CameraActivity的父类布局
// Show our UI over NativeActivity window
_popupWindow.showAtLocation(mainLayout, Gravity.BOTTOM | Gravity.START, 0, 0);
_popupWindow.update();//_popupWindow窗体覆盖NativeActivity的窗体
_takePhoto = (ImageButton) popupView.findViewById(R.id.takePhoto);
_takePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TakePhoto();//JAVA侧通知C++侧,执行拍照操作
}
});
_takePhoto.setEnabled(true);
(popupView.findViewById(R.id.exposureLabel)).setEnabled(true);
(popupView.findViewById(R.id.sensitivityLabel)).setEnabled(true);
SeekBar seekBar = (SeekBar) popupView.findViewById(R.id.exposure_seekbar);
_exposure = new CameraSeekBar(seekBar,
(TextView) popupView.findViewById(R.id.exposureVal),
_initParams[0], _initParams[1], _initParams[2]);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
_exposure.updateProgress(progress);
OnExposureChanged(_exposure.getAbsProgress());//JAVA侧通知C++侧,用户设定的摄像头的曝光值
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
seekBar = ((SeekBar) popupView.findViewById(R.id.sensitivity_seekbar));
_sensitivity = new CameraSeekBar(seekBar,
(TextView) popupView.findViewById(R.id.sensitivityVal),
_initParams[3], _initParams[4], _initParams[5]);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
_sensitivity.updateProgress(progress);
OnSensitivityChanged(_sensitivity.getAbsProgress());//JAVA侧通知C++侧,用户设定的摄像头的灵敏度值
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
} catch (WindowManager.BadTokenException e) {
// UI error out, ignore and continue
Log.e(DBG_TAG, "UI Exception Happened: " + e.getMessage());
}
}});
}
/**
Called from Native side to notify that a photo is taken
*/
public void OnPhotoTaken(String fileName) {//重点,在JAVA侧进行toast提示
final String name = fileName;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),
"Photo saved to " + name, Toast.LENGTH_SHORT).show();
}
});
}
native static void notifyCameraPermission(boolean granted);//重点,JAVA侧通知C++侧,运行时权限授权是否成功
native static void TakePhoto();//重点,JAVA侧通知C++侧,执行拍照操作
native void OnExposureChanged(long exposure);//JAVA侧通知C++侧,用户设定的摄像头的曝光值
native void OnSensitivityChanged(long sensitivity);//JAVA侧通知C++侧,用户设定的摄像头的灵敏度值
static {
System.loadLibrary("ndk_camera");//重点,从JAVA层通知ART,动态加载JNI的共享库
}
}
3、android_main.cpp
D:\WorkSpace\Android\AndroidLearn\GitHubRepo\ndk-samples\camera\basic\src\main\cpp\android_main.cpp
#include "camera_engine.h"
#include "utils/native_debug.h"
/*
* SampleEngine global object
*/
//获取 CameraEngine 实例的函数
static CameraEngine* pEngineObj = nullptr;
CameraEngine* GetAppEngine(void) {
ASSERT(pEngineObj, "AppEngine has not initialized");
return pEngineObj;
}
/**
* Teamplate function for NativeActivity derived applications
* Create/Delete camera object with
* INIT_WINDOW/TERM_WINDOW command, ignoring other event.
*/
//重点,响应 application 每个配置变化的函数
static void ProcessAndroidCmd(struct android_app* app, int32_t cmd) {
CameraEngine* engine = reinterpret_cast<CameraEngine*>(app->userData);
switch (cmd) {
case APP_CMD_INIT_WINDOW://初始化 application 的窗体
if (engine->AndroidApp()->window != NULL) {
engine->SaveNativeWinRes(ANativeWindow_getWidth(app->window),
ANativeWindow_getHeight(app->window),
ANativeWindow_getFormat(app->window));
engine->OnAppInitWindow();
}
break;
case APP_CMD_TERM_WINDOW://销毁 application 的窗体
engine->OnAppTermWindow();
ANativeWindow_setBuffersGeometry(
app->window, engine->GetSavedNativeWinWidth(),
engine->GetSavedNativeWinHeight(), engine->GetSavedNativeWinFormat());
break;
case APP_CMD_CONFIG_CHANGED://响应 application 的配置变化,比如显示方向变化、语言变化等
engine->OnAppConfigChange();
break;
case APP_CMD_LOST_FOCUS:
break;
}
}
//重点,C++层面的 android_main 主函数
extern "C" void android_main(struct android_app* state) {
CameraEngine engine(state);
pEngineObj = &engine;
state->userData = reinterpret_cast<void*>(&engine);//给android_app的结构体成员赋值为 CameraEngine
state->onAppCmd = ProcessAndroidCmd;//给android_app的结构体成员赋值为 ProcessAndroidCmd
// loop waiting for stuff to do.
while (!state->destroyRequested) {//Application的生命周期一直存在,一直轮询
struct android_poll_source* source = nullptr;
auto result = ALooper_pollOnce(0, NULL, nullptr, (void**)&source);//开始轮询函数
ASSERT(result != ALOOPER_POLL_ERROR, "ALooper_pollOnce returned an error");
if (source != NULL) {
source->process(state, source);//轮询处理
}
pEngineObj->DrawFrame();//处理YUV到RGBA格式图像的渲染
}
LOGI("CameraEngine thread destroy requested!");
engine.DeleteCamera();
pEngineObj = nullptr;
}
/**
* Handle Android System APP_CMD_INIT_WINDOW message
* Request camera persmission from Java side
* Create camera object if camera has been granted
*/
//初始化APP窗体的资源
void CameraEngine::OnAppInitWindow(void) {
if (!cameraGranted_) {
// Not permitted to use camera yet, ask(again) and defer other events
RequestCameraPermission();//如果运行时权限没有授权,从C++侧开始请求运行时权限
return;
}
rotation_ = GetDisplayRotation();//获取Display的旋转角度
CreateCamera();//创建 Camera的实例
ASSERT(camera_, "CameraCreation Failed");
EnableUI();//重点,从C++侧调用到JAVA层,刷新Camera的界面
// NativeActivity end is ready to display, start pulling images
cameraReady_ = true;
camera_->StartPreview(true);//启动Camera,开始预览摄像头的图像
}
/**
* Handle APP_CMD_TEMR_WINDOW
*/
//销毁Application关联的Camera实例
void CameraEngine::OnAppTermWindow(void) {
cameraReady_ = false;
DeleteCamera();
}
/**
* Handle APP_CMD_CONFIG_CHANGED
*/
//重点,Application的配置发生变化,重新配置Camera的窗体
void CameraEngine::OnAppConfigChange(void) {
int newRotation = GetDisplayRotation();
if (newRotation != rotation_) {
OnAppTermWindow();
rotation_ = newRotation;
OnAppInitWindow();
}
}
/**
* Retrieve saved native window width.
* @return width of native window
*/
int32_t CameraEngine::GetSavedNativeWinWidth(void) {
return savedNativeWinRes_.width;
}
/**
* Retrieve saved native window height.
* @return height of native window
*/
int32_t CameraEngine::GetSavedNativeWinHeight(void) {
return savedNativeWinRes_.height;
}
/**
* Retrieve saved native window format
* @return format of native window
*/
int32_t CameraEngine::GetSavedNativeWinFormat(void) {
return savedNativeWinRes_.format;
}
/**
* Save original NativeWindow Resolution
* @param w width of native window in pixel
* @param h height of native window in pixel
* @param format
*/
//重点,保存 本地窗口的宽度、高度、图像格式
void CameraEngine::SaveNativeWinRes(int32_t w, int32_t h, int32_t format) {
savedNativeWinRes_.width = w;
savedNativeWinRes_.height = h;
savedNativeWinRes_.format = format;
}
4、camera_ui.cpp
//重点:定义JAVA层和C++层的JNI接口,侧重点从C++层到JAVA层的接口
D:\WorkSpace\Android\AndroidLearn\GitHubRepo\ndk-samples\camera\basic\src\main\cpp\camera_ui.cpp
#include <utils/native_debug.h>
#include "camera_engine.h"//
/**
* Retrieve current rotation from Java side
*
* @return current rotation angle//一部分CameraEngine的函数在此处定义。
*/
int CameraEngine::GetDisplayRotation() {//重点,从C++层获取JAVA层的G-Sensor的角度值
ASSERT(app_, "Application is not initialized");
JNIEnv *env;//JNI的系统环境
ANativeActivity *activity = app_->activity;//实例化ANativeActivity
activity->vm->GetEnv((void **)&env, JNI_VERSION_1_6);//获取VM虚拟机的环境
activity->vm->AttachCurrentThread(&env, NULL);//抢占当前的线程
jobject activityObj = env->NewGlobalRef(activity->clazz);//实例化JAVA侧的ANativeActivity类名
jclass clz = env->GetObjectClass(activityObj);
jint newOrientation = env->CallIntMethod(
activityObj, env->GetMethodID(clz, "getRotationDegree", "()I"));//获取newOrientation,调用JAVA侧的方法名,传入方法名的参数
env->DeleteGlobalRef(activityObj);
activity->vm->DetachCurrentThread();//释放当前的线程
return newOrientation;
}
/**//重点,从C++传递摄像头的曝光、灵敏度参数到JAVA侧的方法
* Initializate UI on Java side. The 2 seekBars' values are passed in
* array in the tuple of ( min, max, curVal )
* 0: exposure min
* 1: exposure max
* 2: exposure val
* 3: sensitivity min
* 4: sensitivity max
* 5: sensitivity val
*/
const int kInitDataLen = 6;
void CameraEngine::EnableUI(void) {//一部分CameraEngine的函数在此处定义。
JNIEnv *jni;
app_->activity->vm->AttachCurrentThread(&jni, NULL);
int64_t range[3];
//