天天看點

Android多媒體應用開發系列(一) 拍照

辛苦堆砌,轉載請注明出處,謝謝大家的支援。

       前段時間工作很忙,忙了也好,開始思考自己到底應該如何工作,如何生活。前段時間的項目主要圍繞Android,MFC,Winform開展,每個項目基本上都進行過一輪架構調整,是以對這些方面都有了比較深刻的了解,最近閑下來了,有時間,把前段時間項目的東西做一些整理,友善查閱,也提供一個大家共同學習的平台。

       首先從Android的開始吧,比較熱門嘛,主要是使用了Android多媒體開發的各個部分(Camera,Surface,MediaPlayer,MediaRecorder,MediaCodec等),知識也算系統了,基本都用到了,是以考慮寫成一個系列,先從基本的拍照走起。講原理貼代碼想必大家也受夠了,不如換一種輕松的方式,中間融合上開發的流程結合重構,能夠更好地掌握技能。跳過建立項目等等,直接開始創作。

      Android拍照,就要使用相機Camera,核心的接口

android.hardware.Camera.takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg)
           

參數是三個回調,分别是shutter——快門回調,raw——沒有壓縮過的原始資料回調,jpeg——傳回jpeg資料的回調

這裡有一個小發現,如果将shutter設定為null,将不會發出拍照的“咔嚓”聲,有興趣的可以嘗試一下。

       現在知道了要使用的接口,還有許多準備工作要做,但是好不容易查到的接口該如何是好,先封裝函數吧,留着用(相機記得要釋放)

public class MainActivity extends AppCompatActivity {
	private Camera mCamera;
	...

	@Override
	protected void onStart() {
		super.onStart();
		mCamera = Camera.open();
	}


	@Override
	protected void onStop() {
		if (mCamera != null) {
			mCamera.release();
		}
		super.onStop();
	}
	...

	private void takePicture() {
		mCamera.takePicture(null, null, null);
	}
}
           

清單檔案中添加使用Camera的權限

<!--suppress DeprecatedClassUsageInspection -->
<uses-feature android:name="android.hardware.Camera" />

<uses-permission android:name="android.permission.CAMERA" />
           

       現在有點空虛了?不知道下一步該做什麼了?好吧,那就先把回調填充上,shutter是要添加的,個人比較喜歡那種聲音,jpeg也是需要的,我想把圖檔儲存為jpeg格式,添加接口實作

public class MainActivity extends AppCompatActivity implements Camera.ShutterCallback, Camera.PictureCallback { 
	...
	private void takePicture() {
		mCamera.takePicture(this, null, this);
	}
	
	@Override
	public void onPictureTaken(byte[] data, Camera camera) {
	}


	@Override
	public void onShutter() {
	}
}
           

          接下來,該做什麼?我們的PictureCallback回調給我們資料,但是資料源在哪裡?沒有資料源,當然無法回調資料,這時候要用到一個好東西了——SurfaceView,檢視google的文檔你會發現,Surface,SurfaceView和SurfaceHolder是桃園結義三兄弟,Surface負責圖像的繪制,是一個很底層的類,是以我們一般不直接使用它,而是通過SurfaceHolder來間接操作Surface,SurfaceView則是一個視圖類,是直接呈現給使用者的視圖,承載使用者的操作以及呈現Surface上繪制的内容。我們需要做的就是,先在視圖層添加SurfaceView再說。

<?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.yjp.takepicture.MainActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/takePictureButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_alignBottom="@id/surfaceView"
        android:text="@string/takePictureButtonText"/>
</RelativeLayout>
           

代碼中

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
	
	Button takePictureButton = (Button) findViewById(R.id.takePictureButton);
	takePictureButton.setOnClickListener(new View.OnClickListener() {
		@Override
		public void onClick(View v) {
			takePicture();
		}
	});
}
           

        現在項目已經可以運作了,但是螢幕黑黑的,點選拍照按鈕沒有反應,沒關系,下面就是添加代碼的部分了。

        先讓我們的SurfaceView能夠看到圖像,隻要我們清楚兩點,圖像的來源是Camera,而圖像的呈現要靠SurfaceView,如何把兩者關聯起來才是關鍵,前面有提到,Surface是負責繪制的,我們隻要能把Camera采集的資料送給Surface,就能顯示出攝像頭采集到的資料了, 而我們不直接操作Surface,需要借助于SurfaceHolder才能操作Surface,那麼我們就需要借助于SurfaceHolder将Camera和SurfaceView關聯起來。

public class MainActivity extends AppCompatActivity
        implements Camera.ShutterCallback, Camera.PictureCallback, SurfaceHolder.Callback {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //配置SurfaceView
        //setType使用外來資料源
        //設定SurfaceHolder.Callback
        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        SurfaceHolder surfaceHolder = surfaceView.getHolder();
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        surfaceHolder.addCallback(this);

        ...
    }

	...

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            //surface建立成功能夠拿到回調的holder
            //holder中包含有成功建立的Surface
            //進而交給錄影機預覽使用
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //surface的尺寸發生變化
        //配置預覽參數,如分辨率等
        //這裡使用的分辨率簡單選取了支援的預覽分辨率的第一項
        //網上可以查找對應的優選算法
        Camera.Parameters parameters = mCamera.getParameters();
        List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
        Camera.Size selected = sizes.get(0);
        parameters.setPreviewSize(selected.width, selected.height);
		parameters.setPictureSize(selected.width, selected.height);

        //給錄影機設定參數,開始預覽
        mCamera.setParameters(parameters);
        mCamera.startPreview();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
}
           

運作程式,可以實作預覽。接下來隻剩下拍照了,隻要實作前面提到的PictureCallback jpeg的onPictureTaken即可。

@Override
public void onPictureTaken(byte[] data, Camera camera) {
	try {
		FileOutputStream out;
		String filename = new Date().getTime() + ".jpg";
		String filePathname = Environment.getExternalStorageDirectory() + "/"
				+ Environment.DIRECTORY_PICTURES + "/" + filename;
		out = new FileOutputStream(filePathname);
		out.write(data);
		out.flush();
		out.close();

		//重新啟動預覽
		mCamera.startPreview();
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}
}
           

拍照完成時,相機會停止預覽,記得恢複預覽。

由于需要寫入檔案,在清單檔案中添加存儲通路權限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
           

運作程式,點選拍照按鈕,現在就會在對應的目錄下生成照片了。

      最後,修改MainActivity橫屏無标題欄

<activity
	android:name=".MainActivity"
	android:screenOrientation="landscape"
	android:theme="@style/Theme.AppCompat.Light.NoActionBar">
	<intent-filter>
		<action android:name="android.intent.action.MAIN" />

		<category android:name="android.intent.category.LAUNCHER" />
	</intent-filter>
</activity>
           

至此,完成拍照功能。

       總結一下,拍照使用了Camera和SurfaceView,進而間接使用了SurfaceHolder和Surface,實作了Camera.ShutterCallback, Camera.PictureCallback, SurfaceHolder.Callback 三個接口,合理配置相機的接口調用順序即可。

       下一篇文章将基于目前版本做一些重構,然後實作自動拍照和定時連拍。

源碼已上傳到 這裡

Android多媒體應用開發系列(一) 拍照