MTX20开发板的操作系统是Android,所以APK模式的应用程序才是王道,而不是我上一个帖子里那样使用命令行方式来调用应用程序。Android系统开发APK程序有几种方法:1)在Android源代码里面开发APK;2)Eclipse+ADT+Android SDK+NDK开发APK;3)Android Studio+NDK开发APK。第一种方法自己只是尝试过修改一些Android自带App而已。第二种方法好几年前用过,只记得更新一次SDK真的好慢,还看运气。第三种方法是谷歌近来推荐的方式,当然此方法具有谷歌软件的标志“零碎”,测试版,正式版,1.0、2.0、2.2、2.2.2、等等,每个版本又有许多不同,比如我下载的版本2.2.2,与前面的版本相比改动蛮大滴。
不同于Android系统源代码下载的各种不容易,Android Studio下载简单,看了谷歌为了这个软件肯定做了不少功夫。下载地址为: http://www.android-studio.org/index.php/download,我下载的版本:android-studio-bundle-145.3360264-windows.exe。安装也比以前变得简单多了,不需要安装JDK直接安装即可。
Android Studio安装过程比较简单,只要注意安装的路径即可,我这里默认在C盘。
Android Studio初始化过程如下,我选择的安装方式是“Custom",避免默认方式安装过多无用的东西。虚拟机有761M大小,可能对于有真机的情况也用处不大。
Android Studio添加SDK和NDK等必要库文件
在上个帖子C语言访问USB Camera设备的基础上暴露JNI接口给Java调用。
JNI关键函数介绍:
1)Java_com_example_administrator_simplewebcam_MainActivity_prepareCamera函数被用于初始化USB Camera设备。
- extern "C"
- jint
- Java_com_example_administrator_simplewebcam_MainActivity_prepareCamera( JNIEnv* env,jobject thiz, jint videoid){
- int ret;
- if(camerabase<0){
- camerabase = checkCamerabase();
- }
- ret = opendevice(camerabase + videoid);
- if(ret != ERROR_LOCAL){
- ret = initdevice();
- }
- if(ret != ERROR_LOCAL){
- ret = startcapturing();
- if(ret != SUCCESS_LOCAL){
- stopcapturing();
- uninitdevice ();
- closedevice ();
- LOGE("device resetted");
- }
- }
- if(ret != ERROR_LOCAL){
- rgb = (int *)malloc(sizeof(int) * (IMG_WIDTH*IMG_HEIGHT));
- ybuf = (int *)malloc(sizeof(int) * (IMG_WIDTH*IMG_HEIGHT));
- }
- return ret;
- }
复制代码
2)Java_com_example_administrator_simplewebcam_MainActivity_pixeltobmp函数用于数据转换,USB Camera设备出来的数据是YUV或者MJPG形式,想要显示在Android的界面上必须转化为BMP或者个RGB格式,这里的实现主要来自网上开源代码simplewebcam,根据实际需要做了一些修改。
- void
- Java_com_example_administrator_simplewebcam_MainActivity_pixeltobmp( JNIEnv* env,jobject thiz,jobject bitmap){
- jboolean bo;
- AndroidBitmapInfo info;
- void* pixels;
- int ret;
- int i;
- int *colors;
- int width=0;
- int height=0;
- if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
- LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
- return;
- }
- width = info.width;
- height = info.height;
- if(!rgb || !ybuf) return;
- if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
- LOGE("Bitmap format is not RGBA_8888 !");
- return;
- }
- if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
- LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
- }
- colors = (int*)pixels;
- int *lrgb =NULL;
- lrgb = &rgb[0];
- for(i=0 ; i<width*height ; i++){
- *colors++ = *lrgb++;
- }
- AndroidBitmap_unlockPixels(env, bitmap);
- }
复制代码
3)Java_com_example_administrator_simplewebcam_MainActivity_processCamera函数开始预览。
- void
- Java_com_example_administrator_simplewebcam_MainActivity_processCamera( JNIEnv* env,
- jobject thiz){
- readframeonce();
- }
复制代码
4)Java_com_example_administrator_simplewebcam_MainActivity_stopCamera函数关闭USB Camera设备。
- void
- Java_com_example_administrator_simplewebcam_MainActivity_stopCamera(JNIEnv* env,jobject thiz){
- stopcapturing ();
- uninitdevice ();
- closedevice ();
- if(rgb) free(rgb);
- if(ybuf) free(ybuf);
- fd = -1;
- }
复制代码
5)因为在代码中使用了AndroidBitmap_getInfo等Bitmap处理函数,所以需要在CMakeLists.txt中添加jnigraphics库支持。
- # Sets the minimum version of CMake required to build the native
- # library. You should either keep the default value or only pass a
- # value of 3.4.0 or lower.
- 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 it for you.
- # Gradle automatically packages shared libraries with your APK.
- add_library( # Sets the name of the library.
- ImageProc
- # Sets the library as a shared library.
- SHARED
- # Provides a relative path to your source file(s).
- # Associated headers in the same location as their source
- # file are automatically included.
- src/main/cpp/native-lib.cpp )
- # Searches for a specified prebuilt library and stores the path as a
- # variable. Because system libraries are included 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 jnigraphics )
- # Specifies libraries CMake should link to your target library. You
- # can link multiple libraries, such as libraries you define in the
- # build script, prebuilt third-party libraries, or system libraries.
- target_link_libraries( # Specifies the target library.
- ImageProc
- # Links the target library to the log library
- # included in the NDK.
- ${log-lib} jnigraphics )
复制代码
6)activity_main.xml,界面设计代码
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/activity_main"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context="com.example.administrator.simplewebcam.MainActivity">
- <TextView
- android:id="@+id/sample_text"
- android:layout_width="150dp"
- android:layout_height="50dp"
- android:text="Hello World! C++" />
- <Button android:layout_toRightOf="@id/sample_text"
- android:layout_width="80dp"
- android:layout_height="50dp"
- android:text="btn1" android:id="@+id/btn1"/>
- <Button android:layout_toRightOf="@id/btn1"
- android:layout_width="80dp"
- android:layout_height="50dp"
- android:text="btn2" android:id="@+id/btn2"/>
- <SurfaceView android:layout_below="@id/sample_text"
- android:layout_alignLeft="@id/sample_text"
- android:layout_alignParentTop="false"
- android:layout_alignParentLeft="true"
- android:layout_alignParentRight="true"
- android:layout_alignParentBottom="true"
- android:id="@+id/MySurface"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" />
- </RelativeLayout>
复制代码
7)MainActivity.java代码
- package com.example.administrator.simplewebcam;
- import android.Manifest;
- import android.content.Context;
- import android.content.DialogInterface;
- import android.content.pm.PackageManager;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.Paint;
- import android.graphics.PorterDuff;
- import android.graphics.PorterDuffXfermode;
- import android.graphics.Rect;
- import android.graphics.RectF;
- import android.os.Environment;
- import android.support.annotation.NonNull;
- import android.support.v4.app.ActivityCompat;
- import android.support.v4.content.ContextCompat;
- import android.support.v7.app.AlertDialog;
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.SurfaceHolder;
- import android.view.SurfaceView;
- import android.widget.Button;
- import android.widget.TextView;
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Toast;
- import java.io.File;
- import java.io.IOException;
- import java.lang.reflect.Field;
- import java.util.ArrayList;
- import static android.graphics.BitmapFactory.*;
- public class MainActivity extends AppCompatActivity {
- public SurfaceHolder MyHolder;
- private static final boolean DEBUG = true;
- private static final String TAG="WebCam";
- protected Context context;
- Thread mainLoop = null;
- private Bitmap bmp=null;
- private boolean cameraExists=false;
- private boolean shouldStop=false;
- // /dev/videox (x=cameraId+cameraBase) is used.
- // In some omap devices, system uses /dev/video[0-3],
- // so users must use /dev/video[4-].
- // In such a case, try cameraId=0 and cameraBase=4
- private int cameraId=0;
- private int cameraBase=0;
- // This definition also exists in ImageProc.h.
- // Webcam must support the resolution 640x480 with YUYV format.
- static final int IMG_WIDTH=640;
- static final int IMG_HEIGHT=480;
- // The following variables are used to draw camera images.
- private int winWidth=0;
- private int winHeight=0;
- private Rect rect;
- private int dw, dh;
- private float rate;
- // JNI functions
- public native int prepareCamera(int videoid);
- public native int prepareCameraWithBase(int videoid, int camerabase);
- public native void processCamera();
- public native void stopCamera();
- public native void pixeltobmp(Bitmap bitmap);
- public native void testLog();
- public native String stringFromJNI1();
- static {
- System.loadLibrary("ImageProc");
- }
- SurfaceView MysurfaceView;
- private static final int MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
- private boolean mExternalStorageAvailable = false;
- private boolean mExternalStorageWriteable = false;
- private static final int CAMERA_REQUEST_CODE = 1;
- private boolean mCamera = false;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Button btn1 = (Button) findViewById(R.id.btn1);
- Button btn2 = (Button) findViewById(R.id.btn2);
- MysurfaceView = (SurfaceView)findViewById(R.id.MySurface);
- MyHolder = MysurfaceView.getHolder();
- btn1.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- }
- });
- btn2.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- }
- });
- // Example of a call to a native method
- TextView tv = (TextView) findViewById(R.id.sample_text);
- tv.setText(stringFromJNI1());
- int nRet = 0;//
- SetExternalStoragepermission_OK();
- nRet = CheckExternalStoragepermission_OK();
- if(nRet == 0)
- {
- Log.i("Surface:", "nRet ==0");
- }
- else
- {
- Log.i("Surface:", "nRet !=0");
- MyHolder.addCallback(new MyCallBack());// surfaceCreated surfaceChanged
- }
- //
- }
- private int CheckExternalStoragepermission_OK()
- {
- String externalStorageState = Environment.getExternalStorageState();
- if (Environment.MEDIA_MOUNTED.equals(externalStorageState)) {
- mExternalStorageAvailable = true;
- mExternalStorageWriteable = true;
- return 2; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(externalStorageState)) {
- mExternalStorageAvailable = true;
- mExternalStorageWriteable = false;
- return 1; } else {
- mExternalStorageAvailable = mExternalStorageWriteable = false;
- return 0;
- }
- // handleExternalStorageState(mExternalStorageAvailable,mExternalStorageWriteable);
- }
- private void SetExternalStoragepermission_OK()
- {
- String externalStorageState = Environment.getExternalStorageState();
- if (Environment.MEDIA_MOUNTED.equals(externalStorageState)) {
- mExternalStorageAvailable = true;
- mExternalStorageWriteable = true;
- } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(externalStorageState)) {
- mExternalStorageAvailable = true;
- mExternalStorageWriteable = false;
- } else {
- mExternalStorageAvailable = mExternalStorageWriteable = false;
- }
- handleExternalStorageState(mExternalStorageAvailable,mExternalStorageWriteable);
- requestPermission();
- }
- private void requestPermission() {
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
- != PackageManager.PERMISSION_GRANTED) {
- // 第一次请求权限时,用户如果拒绝,下一次请求shouldShowRequestPermissionRationale()返回true
- // 向用户解释为什么需要这个权限
- if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
- new AlertDialog.Builder(this)
- .setMessage("申请相机权限")
- .setPositiveButton("确定", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- //申请相机权限
- ActivityCompat.requestPermissions(MainActivity.this,
- new String[]{Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE);
- }
- })
- .show();
- } else {
- //申请相机权限
- ActivityCompat.requestPermissions(this,
- new String[]{Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE);
- }
- } else {
- // Tv.setTextColor(Color.GREEN);
- // Tv.setText("相机权限已申请");
- }
- }
- private void handleCameraState(boolean mCamera ) {
- }
- private void handleExternalStorageState(boolean mExternalStorageAvailable,boolean mExternalStorageWriteable ) {
- if(mExternalStorageAvailable && mExternalStorageWriteable){
- //STORAGE is dangerous so check
- // checkSelfPermission()
- int permissionCheck = ContextCompat.checkSelfPermission(this,
- Manifest.permission.WRITE_EXTERNAL_STORAGE);
- Log.d("permission", "permissionCheck=="+permissionCheck);
- if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
- //has not granted request permission
- // Should we show an explanation?
- if (ActivityCompat.shouldShowRequestPermissionRationale(this,
- Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
- // Show an expanation to the user *asynchronously* -- don't block
- // this thread waiting for the user's response! After the user
- // sees the explanation, try again to request the permission.
- // Toast.makeText(MainActivity.this, "Permission++++", Toast.LENGTH_SHORT).show();
- // ActivityCompat.requestPermissions(this,
- // new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- // MY_PERMISSIONS_REQUEST_READ_CONTACTS);
- new AlertDialog.Builder(this)
- .setMessage("您拒绝过授予访问外部存储设备的权限,但是只有申请该权限,才能往外部存储设备写入数据,你确定要重新申请获取权限吗?")
- .setPositiveButton("ok", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- //again request permission
- ActivityCompat.requestPermissions(MainActivity.this,
- new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
- }
- })
- .setNegativeButton("no", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- })
- .create()
- .show();
- } else {
- // No explanation needed, we can request the permission.
- ActivityCompat.requestPermissions(this,
- new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
- // MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE is an
- // app-defined int constant. The callback method gets the
- // result of the request.
- }
- }else{
- //had granted
- Toast.makeText(MainActivity.this, "run writeDatasToExternalStorage() method", Toast.LENGTH_SHORT).show();
- // writeDatasToExternalStorage();
- }
- }else{
- Log.d("permission", "ExternalStorage can not be write or unAvailable");
- }
- }
- /** */
- private void writeDatasToExternalStorage(){
- File directory = Environment.getExternalStorageDirectory();// 6.0 /storage/emulated/0 5.0 /storage/sdcard
- /*
- Environment.getDataDirectory();// /data
- Environment.getRootDirectory();// /system
- Environment.getDownloadCacheDirectory();// /cache
- Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES);// 6.0 /storage/emulated/0/Pictures 5.0/storage/sdcard/Pictures
- */
- File file = new File(directory, "permisson");
- if(!file.exists()){
- try {
- file.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- *
- * @param requestCode
- * @param permissions
- * @param grantResults
- */
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- Log.d("permission", "onRequestPermissionsResult requestCode" + requestCode);
- if (requestCode == MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) {
- // If request is cancelled, the result arrays are empty.
- if (grantResults.length > 0
- && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // permission was granted, yay! Do the
- // contacts-related task you need to do.
- Toast.makeText(MainActivity.this, "run writeDatasToExternalStorage() method", Toast.LENGTH_SHORT).show();
- // writeDatasToExternalStorage();
- } else {
- // Permission Denied
- Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show();
- // permission denied, boo! Disable the
- // functionality that depends on this permission.
- }
- return;
- }
- if (requestCode == CAMERA_REQUEST_CODE) {
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // Tv.setTextColor(Color.GREEN);
- // Tv.setText("相机权限已申请");
- } else {
- //用户勾选了不再询问
- //提示用户手动打开权限
- if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
- Toast.makeText(this, "相机权限已被禁止", Toast.LENGTH_SHORT).show();
- }
- }
- }
- // other 'case' lines to check for other
- // permissions this app might request
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
- class MyCallBack implements SurfaceHolder.Callback , Runnable {
- @Override
- public void run() {
- while (true && cameraExists) {
- //obtaining display area to draw a large image
- if(winWidth==0){
- winWidth=MysurfaceView.getWidth();
- winHeight=MysurfaceView.getHeight();
- if(winWidth*3/4<=winHeight){
- dw = 0;
- dh = (winHeight-winWidth*3/4)/2;
- rate = ((float)winWidth)/IMG_WIDTH;
- rect = new Rect(dw,dh,dw+winWidth-1,dh+winWidth*3/4-1);
- }else{
- dw = (winWidth-winHeight*4/3)/2;
- dh = 0;
- rate = ((float)winHeight)/IMG_HEIGHT;
- rect = new Rect(dw,dh,dw+winHeight*4/3 -1,dh+winHeight-1);
- }
- }
- // obtaining a camera image (pixel data are stored in an array in JNI).
- processCamera();
- // camera image to bmp
- pixeltobmp(bmp);
- Canvas canvas = MyHolder.lockCanvas();
- if (canvas != null)
- {
- // draw camera bmp on canvas
- canvas.drawBitmap(bmp,null,rect,null);
- MyHolder.unlockCanvasAndPost(canvas);
- }
- if(shouldStop){
- shouldStop = false;
- break;
- }
- }
- }
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width,
- int height) {
- Log.i("Surface:", "Change");
- }
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- Log.i("Surface:", "Create");
- String TAG1 = "test1111";
- Log.d(TAG, "surfaceCreated");
- if(bmp==null){
- bmp = Bitmap.createBitmap(IMG_WIDTH, IMG_HEIGHT, Bitmap.Config.ARGB_8888);
- }
- if(DEBUG) Log.d(TAG, "-10-prepareCamera surfaceCreated" + "TAG1=" + TAG1);
- if(DEBUG) Log.d(TAG, "-11-prepareCamera surfaceCreated cameraId="+ cameraId + "cameraBase="+ cameraBase);
- testLog();
- if(DEBUG) Log.d(TAG, "-12-prepareCamera surfaceCreated cameraId="+ cameraId + "cameraBase="+ cameraBase);
- int ret = prepareCamera(cameraId);
- // int ret = prepareCameraWithBase(cameraId, cameraBase);
- if(DEBUG) Log.d(TAG, "prepareCamera surfaceCreated cameraId="+ cameraId + "cameraBase="+ cameraBase + "ret=" + ret);
- if(ret!=-1) cameraExists = true;
- mainLoop = new Thread(this);
- mainLoop.start();
- }
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- Log.i("Surface:", "Destroy");
- if(DEBUG) Log.d(TAG, "surfaceDestroyed");
- if(cameraExists){
- shouldStop = true;
- while(shouldStop){
- try{
- Thread.sleep(100); // wait for thread stopping
- }catch(Exception e){}
- }
- }
- stopCamera();
- }
- }
- /**
- * A native method that is implemented by the 'native-lib' native library,
- * which is packaged with this application.
- */
- }
复制代码
运行抓图如下:
总结:
1)JNI中定义函数名与Android Studio中Java程序调用的函数名的对应关系:
Java_com_example_administrator_simplewebcam_MainActivity_pixeltobmp pixeltobmp
注意:(1)前者中‘_’是特殊分隔符不能在使用在函数名中,我就遇到了这个坑。
(2)pixeltobmp函数在哪个类里面调用,这个类名必须包含在JNI函数名的定义里面。
2)Android Studio开发的APK程序如何访问/dev/video0等类似设备节点,可以参考下面的方法 ,在这里自己验证了很多方法,后面会专门开一个帖子记录一下。
The permission of /dev/video0 is set 0666 in /ueventd.xxxx.rc
Working