package com.example.
(プロジェクト名);
import android.content.Context;
import android.content.res.Configuration;
import android.content.pm.PackageManager;
import android.app.NativeActivity;
import android.widget.Toast;
import android.Manifest;
import android.util.Size;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraDevice.StateCallback;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.TotalCaptureResult;
import android.media.Image;
import android.media.ImageReader;
import android.media.ImageReader.OnImageAvailableListener;
import android.view.Surface;
public class MainActivity extends NativeActivity
{
String mCameraId;
boolean mIsPortraitDevice;
CameraManager mCameraManager;
CameraCaptureSession mCameraCaptureSession;
CaptureRequest.Builder mCaptureRequestBuilder;
ImageReader mImageReader;
// カメラ映像の解像度
int mCameraImageSizeX;
int mCameraImageSizeY;
// カメラ映像のYUVイメージのY要素を格納する配列とUV要素を格納する配列
byte mCameraYImage[];
byte mCameraUVImage[];
// カメラ映像のYUVフォーマット 1:NV21 2:NV12 3:I420
int mImageFormat;
// カメラの回転角度 0:0度 1:90度 2:180度 3:270度
int mCameraRotation;
// カメラの状態( 0:準備中 1:イメージ取得開始 2:エラー )
int mCameraState;
// カメラ情報取得の権限をリクエストする際に使用する識別番号
static final int PERMISSIONS_REQUEST_CAMERA = 2 ;
// ByteBuffer の実効アドレスを取得する関数
private static long GetByteBufferAddress( final ByteBuffer buffer )
{
try {
// Buffer クラスのメンバー変数 effectiveDirectAddress へアクセスする為の Field を取得
final java.lang.reflect.Field field = java.nio.Buffer.class.getDeclaredField( "effectiveDirectAddress" );
// 値へのアクセスを可能にする
field.setAccessible( true );
// 実効アドレスを取得して返す
return field.getLong( buffer );
} catch( NoSuchFieldException e ) {
e.printStackTrace();
// 例外が発生したらコールスタックをログに出力する
} catch( IllegalAccessException e ) {
e.printStackTrace();
// 例外が発生したらコールスタックをログに出力する
}
return 0;
}
// カメラマネージャの初期化を行う
private void InitializeCameraManager()
{
// スマートフォンかどうかを調べておく
boolean isPortraitApp = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
int orientation = getWindowManager().getDefaultDisplay().getRotation();
if( isPortraitApp )
{
mIsPortraitDevice = ( orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180 );
}
else
{
mIsPortraitDevice = ( orientation == Surface.ROTATION_90 || orientation == Surface.ROTATION_270 );
}
// カメラマネージャの取得
mCameraManager = ( CameraManager )getSystemService( Context.CAMERA_SERVICE );
try {
// 有効なカメラが見つかったかどうかのフラグを倒しておく
boolean Flag = false;
// 搭載されているカメラのID配列を取得
String[] CameraIDs = mCameraManager.getCameraIdList();
// カメラのIDの数だけループ
for( String cameraId : CameraIDs )
{
// カメラの情報を取得
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics( cameraId );
// 背面カメラ以外はキャンセルする
if( characteristics.get( CameraCharacteristics.LENS_FACING ) == CameraCharacteristics.LENS_FACING_BACK )
{
StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP );
// カメラが対応しているサイズの配列を取得
Size[] CameraImageSizes = map.getOutputSizes( SurfaceTexture.class );
// カメラのサイズ 640x480 か 320x240 を使用する
boolean Valid640x480 = false;
boolean Valid320x240 = false;
for( Size cameraSize : CameraImageSizes )
{
if( cameraSize.getWidth() == 320 && cameraSize.getHeight() == 240 )
{
Valid320x240 = true;
}
else
if( cameraSize.getWidth() == 640 && cameraSize.getHeight() == 480 )
{
Valid640x480 = true;
}
}
// 640x480 が有効な場合は 640x480 を、そうでない場合は 320x240 を使用する
if( Valid640x480 )
{
mCameraImageSizeX = 640;
mCameraImageSizeY = 480;
}
else
if( Valid320x240 )
{
mCameraImageSizeX = 320;
mCameraImageSizeY = 240;
}
else
{
// 640x480 にも 320x240 にも対応していなかったらキャンセル
continue;
}
// カメラのIDを保存
mCameraId = cameraId;
// 有効なカメラがあったかどうかのフラグを立てる
Flag = true;
break;
}
}
// 有効なカメラが無かったらここで終了
if( Flag == false )
{
return;
}
} catch( CameraAccessException e ) {
e.printStackTrace();
// 例外が発生したらコールスタックをログに出力する
}
// カメラのYUVイメージのY要素を保存する配列を作成
mCameraYImage = new byte[ mCameraImageSizeX * mCameraImageSizeY ];
// カメラのYUVイメージのUV要素を保存する配列を作成
mCameraUVImage = new byte[ ( mCameraImageSizeX / 2 ) * ( mCameraImageSizeY / 2 ) * 2 ];
try {
// カメラへの接続を開始
mCameraManager.openCamera(
mCameraId,
new StateCallback()
{
// カメラへの接続が完了したら呼ばれる関数
@Override
public void onOpened( CameraDevice cameraDevice )
{
// カメラのイメージを取得する為の ImageReader を作成
mImageReader = ImageReader.newInstance( mCameraImageSizeX, mCameraImageSizeY, ImageFormat.YUV_420_888, 1 );
// カメラのプレビューイメージの取得リクエストの作成
try {
mCaptureRequestBuilder = cameraDevice.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW );
} catch( CameraAccessException e ) {
e.printStackTrace();
// 例外が発生したらコールスタックをログに出力する
}
// カメラのプレビューイメージの取得リクエスト先に作成した ImageReader を追加
mCaptureRequestBuilder.addTarget( mImageReader.getSurface() );
// カメラのプレビューイメージの取得を開始
try {
List< Surface > outputs = Arrays.asList( mImageReader.getSurface() );
cameraDevice.createCaptureSession(
outputs,
new CameraCaptureSession.StateCallback()
{
// イメージの取得が開始されたら呼ばれる関数
@Override
public void onConfigured( CameraCaptureSession session )
{
mCameraCaptureSession = session;
// オートフォーカスモードを設定
mCaptureRequestBuilder.set( CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE );
// フラッシュの設定
mCaptureRequestBuilder.set( CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH );
// イメージの取得を繰り返し行う処理の開始
try {
mCameraCaptureSession.setRepeatingRequest(
mCaptureRequestBuilder.build(),
new CaptureCallback()
{
@Override
public void onCaptureCompleted( CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result )
{
super.onCaptureCompleted( session, request, result );
}
},
null
);
} catch( CameraAccessException e ){
e.printStackTrace();
// 例外が発生したらコールスタックをログに出力する
}
}
@Override
public void onConfigureFailed( CameraCaptureSession session )
{
}
},
null
);
} catch( CameraAccessException e )
{
e.printStackTrace();
// 例外が発生したらコールスタックをログに出力する
}
// ここまで来たらイメージの取得が開始されている
mCameraState = 1;
// ImageReader のイメージが更新されたら呼ばれる処理の設定
mImageReader.setOnImageAvailableListener(
new OnImageAvailableListener()
{
// ImageReader のイメージが更新された呼ばれる関数
@Override
public void onImageAvailable( ImageReader reader )
{
// このタイミングでカメラの向きも取得
{
int orientation = getWindowManager().getDefaultDisplay().getRotation();
switch( orientation )
{
case Surface.ROTATION_0:
mCameraRotation = mIsPortraitDevice ? 3 : 0;
break;
case Surface.ROTATION_90:
mCameraRotation = mIsPortraitDevice ? 0 : 1;
break;
case Surface.ROTATION_180:
mCameraRotation = mIsPortraitDevice ? 1 : 2;
break;
case Surface.ROTATION_270:
mCameraRotation = mIsPortraitDevice ? 2 : 3;
break;
}
}
// ImageReader から Image を取得
Image image = mImageReader.acquireLatestImage();
// カメラのイメージのフォーマットを取得
if( mImageFormat == 0 )
{
// Uイメージのストライドが1byteの場合は I420フォーマット
if( image.getPlanes()[ 1 ].getPixelStride() == 1 )
{
mImageFormat = 3;
}
else
{
ByteBuffer uBuffer = image.getPlanes()[ 1 ].getBuffer();
ByteBuffer vBuffer = image.getPlanes()[ 2 ].getBuffer();
if( GetByteBufferAddress( uBuffer ) < GetByteBufferAddress( vBuffer ) )
{
// UVの順番でイメージが格納されている場合は NV12フォーマット
mImageFormat = 2 ;
}
else
{
// VUの順番でイメージが格納されている場合は NV21フォーマット
mImageFormat = 1 ;
}
}
}
// YUVイメージをクラスの配列にコピーする
{
ByteBuffer ys = image.getPlanes()[ 0 ].getBuffer();
ByteBuffer us = image.getPlanes()[ 1 ].getBuffer();
ByteBuffer vs = image.getPlanes()[ 2 ].getBuffer();
// Yイメージは全フォーマット共通
int i ;
int loopNum = mCameraImageSizeX * mCameraImageSizeY ;
for( i = 0 ; i < loopNum ; i ++ )
{
mCameraYImage[ i ] = ys.get( i );
}
// UVイメージはフォーマットによって処理が異なる
int j = 0 ;
loopNum = ( mCameraImageSizeX / 2 ) * ( mCameraImageSizeY / 2 ) ;
switch( mImageFormat )
{
case 1:
// I420
case 2:
// NV12
for( i = 0 ; i < loopNum ; i ++ )
{
mCameraUVImage[ j + 0 ] = us.get( j );
mCameraUVImage[ j + 1 ] = vs.get( j );
j += 2 ;
}
break;
case 3:
// NV21
for( i = 0 ; i < loopNum ; i ++ )
{
mCameraUVImage[ j + 0 ] = us.get( i );
mCameraUVImage[ j + 1 ] = vs.get( i );
j += 2 ;
}
break;
}
}
// Image の解放
image.close();
}
},
null
);
}
@Override
public void onError(CameraDevice camera, int error)
{
}
@Override
public void onDisconnected(CameraDevice camera)
{
}
},
null
);
} catch( CameraAccessException e ) {
e.printStackTrace();
// 例外が発生したらコールスタックをログに出力する
}
}
// 権限の許可を求めるダイアログで許可か不許可が選択されたら呼ばれる関数
public void onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults )
{
// カメラ情報取得の権限を求めるリクエストに対する結果の場合のみ処理を行う
if( requestCode == PERMISSIONS_REQUEST_CAMERA )
{
// 許可されたのかどうかを判定
if( grantResults[ 0 ] == PackageManager.PERMISSION_GRANTED )
{
// 許可されたらカメラマネージャを初期化
Toast.makeText( MainActivity.this, "カメラ情報取得が許可されました", Toast.LENGTH_SHORT ).show() ;
InitializeCameraManager() ;
}
else
{
// 許可されなかったらその旨を表示する
Toast.makeText( MainActivity.this, "カメラ情報取得が拒否されました", Toast.LENGTH_SHORT ).show() ;
// カメラ使用不可の状態にする
mCameraState = 2 ;
}
}
}
// カメラの処理を開始する
public void StartCamera()
{
// UIスレッドで実行する処理を登録する
runOnUiThread(
new Runnable()
{
// UIスレッドで呼ばれる関数
@Override public void run()
{
// Android のバージョンチェック
if( android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M )
{
// Android 6.0以上の場合はアプリ実行中に位置情報取得の権限があるかをチェックする
// カメラ情報取得の権限があるか判定
if( checkSelfPermission( Manifest.permission.CAMERA ) == PackageManager.PERMISSION_GRANTED )
{
// カメラ情報取得の権限があればカメラマネージャを初期化
Toast.makeText( MainActivity.this, "このアプリはカメラ情報取得の権限が既にあります", Toast.LENGTH_SHORT ).show() ;
InitializeCameraManager() ;
}
else
{
// カメラ情報取得の権限が無ければ権限を求めるダイアログを表示
requestPermissions(
new String[]{ Manifest.permission.CAMERA },
PERMISSIONS_REQUEST_CAMERA
) ;
}
}
else
{
// Android 6.0未満の場合はアプリ実行時にはカメラ情報取得の権限が許可されているので
// 無条件でカメラマネージャの初期化を行う
InitializeCameraManager() ;
}
}
}
) ;
}
}
( (プロジェクト名)となっている箇所は、お手元のプロジェクトの名前を入力してください )
3.Java の情報を取得する C++ のコードを入力する
次に Java のカメラの映像取得の処理を開始する関数を呼び出したり、カメラの状態やカメラの映像を画面に表示したりする C++ 側のプログラムです。
『DXライブラリ Android版』でのみ使用できる GetNativeActivity という関数を使用しています。
#include "DxLib.h"
// Java のクラスの int型のメンバー変数を取得する関数
int GetJavaClassIntValue( const ANativeActivity *NativeActivity, JNIEnv *env, jclass Class, const char *Name )
{
return env->GetIntField( NativeActivity->clazz, env->GetFieldID( Class, Name, "I" ) ) ;
}
int android_main( void )
{
JNIEnv *env ;
const ANativeActivity *NativeActivity ;
int CameraState = 0 ;
int CameraImageSizeX = 0 ;
int CameraImageSizeY = 0 ;
int CameraRotation = -1 ;
int CameraGraphHandle = -1 ;
int CameraSoftImageHandle = -1 ;
// 背景を灰色にする
SetBackgroundColor( 128,128,128 ) ;
SetGraphMode( 640, 960, 32 ) ;
// DXライブラリの初期化
if( DxLib_Init() < 0 ) return -1 ;
// 描画先を裏画面に変更
SetDrawScreen( DX_SCREEN_BACK ) ;
// アプリの NativeActivity を取得しておく
NativeActivity = GetNativeActivity() ;
// Java の関数 StartCamera の呼び出し
{
// JavaVM とソフト実行用スレッドを関連付け( C++ から Java の機能を使用するために必要 )
if( NativeActivity->vm->AttachCurrentThreadAsDaemon( &env, NULL ) != JNI_OK )
{
return -1 ;
}
// Java のクラス MainActivity を取得
jclass jclass_MainActivity = env->GetObjectClass( NativeActivity->clazz ) ;
// Java のクラス MainActivity のメンバー関数 StartCamera の ID を取得
jmethodID jmethodID_StartCamera = env->GetMethodID( jclass_MainActivity, "StartCamera", "()V" ) ;
// Java のクラス MainActivity のメンバー関数 StartCamera の呼び出し
env->CallVoidMethod( NativeActivity->clazz, jmethodID_StartCamera ) ;
// Java のクラス MainActivity の参照を削除
env->DeleteLocalRef( jclass_MainActivity ) ;
// JavaVM とソフト実行用スレッドの関連付け終了
NativeActivity->vm->DetachCurrentThread() ;
}
// メインループ
while( ProcessMessage() == 0 )
{
// 裏画面の内容をクリア
ClearDrawScreen() ;
// JNI 関連の処理
{
// JavaVM とソフト実行用スレッドを関連付け( C++ から Java の機能を使用するために必要 )
if( NativeActivity->vm->AttachCurrentThreadAsDaemon( &env, NULL ) != JNI_OK )
{
return -1 ;
}
// Java のクラス MainActivity を取得
jclass jclass_MainActivity = env->GetObjectClass( NativeActivity->clazz ) ;
// Java のクラス MainActivity のメンバー変数 CameraState の値を取得
CameraState = GetJavaClassIntValue( NativeActivity, env, jclass_MainActivity, "mCameraState" ) ;
// カメラが使用可能な状態になっていたら更に情報を取得する
if( CameraState == 1 )
{
// Java のクラス MainActivity の int型のメンバー変数の値を取得
CameraImageSizeX = GetJavaClassIntValue( NativeActivity, env, jclass_MainActivity, "mCameraImageSizeX" ) ;
CameraImageSizeY = GetJavaClassIntValue( NativeActivity, env, jclass_MainActivity, "mCameraImageSizeY" ) ;
CameraRotation = GetJavaClassIntValue( NativeActivity, env, jclass_MainActivity, "mCameraRotation" ) ;
// Java のクラス MainActivity のメンバー変数 mCameraYImage と mCameraUVImage の ID を取得
jfieldID jfieldID_mCameraYImage = env->GetFieldID( jclass_MainActivity, "mCameraYImage", "[B" ) ;
jfieldID jfieldID_mCameraUVImage = env->GetFieldID( jclass_MainActivity, "mCameraUVImage", "[B" ) ;
// Java のクラス MainActivity のメンバー変数 mCameraYImage と mCameraUVImage の値を取得
{
// ソフトイメージハンドルがまだ作成されていなかったらここで作成する
if( CameraSoftImageHandle == -1 )
{
CameraSoftImageHandle = MakeARGB8ColorSoftImage( CameraImageSizeX, CameraImageSizeY ) ;
}
// YイメージとUVイメージが格納されている配列のJNIオブジェクトを取得
jbyteArray jbyteArray_CameraYImage = ( jbyteArray )env->GetObjectField( NativeActivity->clazz, jfieldID_mCameraYImage );
jbyteArray jbyteArray_CameraUVImage = ( jbyteArray )env->GetObjectField( NativeActivity->clazz, jfieldID_mCameraUVImage );
// JNIオブジェクトからYイメージとUVイメージが格納されているメモリアドレスを取得
jbyte *jbyte_CameraYImage = env->GetByteArrayElements( jbyteArray_CameraYImage, NULL ) ;
jbyte *jbyte_CameraUVImage = env->GetByteArrayElements( jbyteArray_CameraUVImage, NULL ) ;
// ソフトイメージハンドルにYUVイメージをRGBイメージに変換しながら転送
BYTE *yimg = ( BYTE * )jbyte_CameraYImage ;
BYTE *dest = ( BYTE * )GetImageAddressSoftImage( CameraSoftImageHandle ) ;
for( int i = 0 ; i < CameraImageSizeY ; i ++ )
{
BYTE *uvimg = ( BYTE * )jbyte_CameraUVImage + ( i / 2 ) * ( CameraImageSizeX / 2 ) * 2 ;
int k = 0 ;
for( int j = 0 ; j < CameraImageSizeX ; j ++ )
{
int b = ( int )( *yimg + ( 1.732446f * ( ( int )uvimg[ 0 ] - 128 ) ) );
int g = ( int )( *yimg - ( 0.698001f * ( ( int )uvimg[ 1 ] - 128 ) ) - ( 0.337633f * ( ( int )uvimg[ 0 ] - 128 ) ) );
int r = ( int )( *yimg + ( 1.370705f * ( ( int )uvimg[ 1 ] - 128 ) ) );
dest[ 0 ] = ( BYTE )( b < 0 ? 0 : ( b > 255 ? 255 : b ) ) ;
dest[ 1 ] = ( BYTE )( g < 0 ? 0 : ( g > 255 ? 255 : g ) ) ;
dest[ 2 ] = ( BYTE )( r < 0 ? 0 : ( r > 255 ? 255 : r ) ) ;
dest[ 3 ] = 0xff ;
dest += 4 ;
yimg ++ ;
k ++ ;
if( k == 2 )
{
k = 0 ;
uvimg += 2 ;
}
}
}
// YイメージとUVイメージが格納されている配列へのアクセスの終了
env->ReleaseByteArrayElements( jbyteArray_CameraYImage, jbyte_CameraYImage, 0 ) ;
env->ReleaseByteArrayElements( jbyteArray_CameraUVImage, jbyte_CameraUVImage, 0 ) ;
}
}
// Java のクラス MainActivity の参照を削除
env->DeleteLocalRef( jclass_MainActivity ) ;
// JavaVM とソフト実行用スレッドの関連付け終了
NativeActivity->vm->DetachCurrentThread() ;
}
// ソフトイメージハンドルが作成されている場合のみ画像の描画処理を行う
if( CameraSoftImageHandle != -1 )
{
// 既にカメラから取得した画像を描画する為のグラフィックハンドルが作成されているかどうかで処理を分岐
if( CameraGraphHandle == -1 )
{
// まだグラフィックハンドルが作成されていなかったらソフトイメージハンドルからグラフィックハンドルを作成する
CameraGraphHandle = CreateGraphFromSoftImage( CameraSoftImageHandle ) ;
}
else
{
// 既にグラフィックハンドルが作成されていたらソフトイメージハンドルのイメージをグラフィックハンドルに転送
ReCreateGraphFromSoftImage( CameraSoftImageHandle, CameraGraphHandle ) ;
}
// カメラの向きに応じて回転させつつ描画
switch( CameraRotation )
{
case 0:
DrawRotaGraph( 320, 480, 1.0, DX_PI * 2.0, CameraGraphHandle, FALSE ) ;
break;
case 1:
DrawRotaGraph( 320, 480, 1.0, DX_PI * 1.5, CameraGraphHandle, FALSE ) ;
break;
case 2:
DrawRotaGraph( 320, 480, 1.0, DX_PI * 1.0, CameraGraphHandle, FALSE ) ;
break;
case 3:
DrawRotaGraph( 320, 480, 1.0, DX_PI * 0.5, CameraGraphHandle, FALSE ) ;
break;
}
}
// カメラの状態と解像度を描画
DrawFormatString( 0, 0, GetColor( 255,255,255 ), "CameraState : %s Size : %d x %d",
CameraState == 0 ? "準備中" : ( CameraState == 1 ? "使用可能" : "使用不可能" ),
CameraImageSizeX, CameraImageSizeY
) ;
// 裏画面の内容を表画面に反映
ScreenFlip() ;
}
// DXライブラリの後始末
DxLib_End() ;
// ソフトの終了
return 0 ;
}
以上です。
これでプロジェクトを実行すると、画面にカメラが使用可能かや、使用可能な場合はカメラの映像が表示されます。
詳細は割愛しますが、C++ から Java にアクセスする基本的な流れは。
① AttachCurrentThreadAsDaemon でソフト実行用スレッドと JavaVM を関連付け、JNIEnv も取得。
② GetObjectClass でアプリの Java クラスを取得。
③ GetMethodID や GetFieldID で Java の関数や変数の ID を取得。
④ Call???Method や Get???Field で Java の関数の呼び出しや変数の値を取得。
⑤ DeleteLocalRef で Get???Field で取得したオブジェクト系の変数の参照を削除。
⑥ DeleteLocalRef で GetObjectClass で取得したアプリの Java クラスの参照を削除。
⑦ DetachCurrentThread でソフト実行用スレッドと JavaVM の関連付けを終了。
となります。
C++ と Java でのやり取りを行う機能( JNI( Java Native Interface ) ) の詳しい扱い方については、検索サイトで
『Android Java JNI』などのキーワードで検索して調べてみてください。
戻る