先日twitterで開発者さんとこんなやりとりがありまして……
自作カメラを作る時ってSurfaceView使うんだっけ?
作ったことないからわからないんだけど、リフレクションの非公開API呼び出しで無音カメラ作れんの?
— シャー芯 (@shiMnn) 2014, 1月 17
ちょうど今そういうの開発してますが、SurfaceViewですよ。ただそれでも撮影するとカシャ音鳴っちゃうので、setDrawingCacheEnabledでスクリーンショット撮って画像にして保存とかじゃないすかね?(調べてないからテキトー) RT @shiMnn: 自作カメラを
— gucci1208 (@gucci1208) 2014, 1月 17
さくっと15分くらいで作ってみました! 保存処理をちょこっと工夫しています。
○○.setDrawingCacheEnabled(true); Bitmap bitmap = Bitmap.createBitmap(○○.getDrawingCache());
「○○」の部分にViewを入れると、そのViewのキャプチャが取れるので、カメラを展開しているSurfaceViewを当てはめています。
[AndroidManifest.xml]
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
カメラを使うのと、撮影した画像をSDカードに保存するので、このパーミッションを追加してください。
[MainActivity.java]
public class MainActivity extends Activity { private SurfaceView mySurfaceView; private Camera myCamera; private String PARENT_FOLDER_NAME = "gucci1208"; private int default_camera_angle; private int default_image_angle; private SurfaceHolder.Callback mSurfaceListener = new SurfaceHolder.Callback() { public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub myCamera = Camera.open(); // カメラ機能が横向き固定なので変更 myCamera.setDisplayOrientation(default_camera_angle); try { myCamera.setPreviewDisplay(holder); } catch (Exception e) { e.printStackTrace(); } } public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub myCamera.release(); myCamera = null; } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { myCamera.stopPreview(); Parameters mParam = myCamera.getParameters(); //撮った画像も縦向きに mParam.setRotation(default_image_angle); // Set orientation boolean portrait = isPortrait(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { // 2.1 and before if (portrait) { mParam.set("orientation", "portrait"); } else { mParam.set("orientation", "landscape"); } } else { // 2.2 and later if (portrait) { myCamera.setDisplayOrientation(90); } else { myCamera.setDisplayOrientation(0); } } // Set width & height int previewWidth = width; int previewHeight = height; if (portrait) { previewWidth = height; previewHeight = width; } List<Size> sizes = mParam.getSupportedPreviewSizes(); int tmpHeight = 0; int tmpWidth = 0; for (Size size : sizes) { if ((size.width > previewWidth) || (size.height > previewHeight)) { continue; } if (tmpHeight < size.height) { tmpWidth = size.width; tmpHeight = size.height; } } previewWidth = tmpWidth; previewHeight = tmpHeight; mParam.setPreviewSize(previewWidth, previewHeight); // Adjust SurfaceView size ViewGroup.LayoutParams layoutParams = mySurfaceView.getLayoutParams(); float layoutHeight, layoutWidth; if (portrait) { layoutHeight = previewWidth; layoutWidth = previewHeight; } else { layoutHeight = previewHeight; layoutWidth = previewWidth; } float factH, factW, fact; factH = height / layoutHeight; factW = width / layoutWidth; // Select smaller factor, because the surface cannot be set to the size larger than display metrics. if (factH < factW) { fact = factH; } else { fact = factW; } layoutParams.height = (int)(layoutHeight * fact); layoutParams.width = (int)(layoutWidth * fact); mySurfaceView.setLayoutParams(layoutParams); myCamera.setParameters(mParam); myCamera.startPreview(); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.lo_camera); //タッチすると撮影するよメッセージ android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(MainActivity.this); builder.setMessage("画面をタッチすると撮影します").setPositiveButton("OK", null).show(); default_camera_angle = 270; default_image_angle = 90; mySurfaceView = (SurfaceView) findViewById(R.id.surface_view); SurfaceHolder holder = mySurfaceView.getHolder(); holder.addCallback(mSurfaceListener); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (myCamera != null) { //保存処理 SaveScreen(); } } return true; } private void SaveScreen() { mySurfaceView.setDrawingCacheEnabled(true); Bitmap bitmap = Bitmap.createBitmap(mySurfaceView.getDrawingCache()); // SDカードにJPEGデータを保存する if (bitmap != null) { try { // 「/sdcard」を取得 File directory = Environment.getExternalStorageDirectory(); // SDカードに書き込める状態かチェック if (directory.exists()) { // ⇒書き込める状態の場合 if (directory.canWrite()) { // 画像を保存するフォルダを作成します。 File file1 = new File(directory.getAbsolutePath() + "/" + PARENT_FOLDER_NAME); file1.mkdir(); File file2 = new File(directory.getAbsolutePath() + "/" + PARENT_FOLDER_NAME + "/" + getPackageName()); file2.mkdir(); } } // フルパスを取得 String folderpath = directory.getAbsolutePath() + "/" + PARENT_FOLDER_NAME + "/" + getPackageName() + "/"; // sdcardフォルダを指定 File root = new File(folderpath); // 日付でファイル名を作成 String filename = System.currentTimeMillis() + ".jpg"; // 保存処理開始 FileOutputStream fos = null; fos = new FileOutputStream(new File(root, filename)); // jpegで保存 bitmap.compress(CompressFormat.JPEG, 100, fos); // 保存処理終了 fos.close(); finish(); } catch (Exception e) { android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(MainActivity.this); builder.setMessage("撮影した画像の保存に失敗しました").setPositiveButton("OK", null).show(); } } } protected boolean isPortrait() { return (MainActivity.this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT); } }
カメラ機能をSurfaceViewを使って実現しています。
本来なら「SaveScreen();」の部分で、「Camera.ShutterCallback」を呼び出してイメージを生成したりするのですが、そこでSurfaceViewのキャプチャを取得してSDカードに保存しています。
[activity_main.xml]
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#ffffff" android:gravity="center" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#ffffff" android:text="" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:orientation="vertical" > <SurfaceView android:id="@+id/surface_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" /> </LinearLayout> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#ffffff" android:text="" /> </LinearLayout>
レイアウトxmlはこのようになります。単純ですね。
これで[SDカード]-[gucci1208]-[com.gucci1208.silentcamera]フォルダに画像が保存されたはずです。
さて、撮影された画像がこちら。
なんだよこれ……
どうやらSurfaceViewから派生しているVideoViewのビットマップは取得できないようです。なんと!
無音カメラの開発、思ったより難しいみたい。
その2に続く。
コメントを残す