辛苦堆砌,轉載請注明出處,謝謝大家的支援。
前段時間工作很忙,忙了也好,開始思考自己到底應該如何工作,如何生活。前段時間的項目主要圍繞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 三個接口,合理配置相機的接口調用順序即可。
下一篇文章将基于目前版本做一些重構,然後實作自動拍照和定時連拍。
源碼已上傳到 這裡