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_MainActi vity_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){
***oolean 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
*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,界面设计代码
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">
android:id="@+id/sample_text"
android:layout_width="150dp"
android:layout_height="50dp"
android:text="Hello World! C++" />
android:layout_width="80dp"
android:layout_height="50dp"
android:text="btn1" android:id="@+id/btn1"/>
android:layout_width="80dp"
android:layout_height="50dp"
android:text="btn2" android:id="@+id/btn2"/>
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" />
复制代码
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