1).界面佈局,視圖仿一下我手機自帶的相機
2).Camera的簡單使用,雖然Camera已通過時了,但仍是來看一下,由簡入深
下一篇會介紹替代者:Camera2舒適提示:本文多圖預警,請Wifi觀看~
android
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
git
View經過刷新重繪視圖,Android系統經過發出VSYNC信號進行屏幕的重繪,刷新的間隔時間爲16ms。
若是16ms內View完成須要執行的全部操做,在視覺上,不會產生卡頓的感受;反之卡頓。
特別的須要頻繁刷新的界面上,如遊戲(60FPS以上),就會不斷阻塞主線程,從而致使界面卡頓。
複製代碼
比較 | 刷新 | 刷新時線程 | 雙緩衝 |
---|---|---|---|
普通View | 主動 | 僅主線程 | 無 |
SurfaceView | 被動 | 容許子線程 | 有 |
SurfaceView至關因而另外一個繪圖線程,它是不會阻礙主線程,而且它在底層實現機制中實現了雙緩衝機制
一個View須要頻繁的刷新,或者在刷新時數據處理量大(可能引發卡頓),能夠考慮使用SurfaceView來替代。
很明顯相機隨時捕捉畫面,須要頻繁的刷新,使用SurfaceView比較好
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraActivity">
<SurfaceView
android:id="@+id/id_sv_video"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
複製代碼
public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback {
@BindView(R.id.id_sv_video)
SurfaceView mIdSvVideo;
private Camera camera;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mIdSvVideo.getHolder().addCallback(this);
// 打開攝像頭並將展現方向旋轉90度
camera = Camera.open();
camera.setDisplayOrientation(90);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
camera.setPreviewDisplay(holder);//Camera+SurfaceHolder
camera.startPreview();//開啓預覽
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
camera.release();//釋放資源
}
}
複製代碼
這是我仿的佈局,具體怎麼佈局的,不是本篇的要點,本身看源碼吧。github
經測試
camera.startPreview();
以後,PreviewCallback的onPreviewFrame方法會不斷回調
也就是說監聽這個方法就能夠得到連續的幀,這也是視頻數據的來源編程
public interface PreviewCallback{
void onPreviewFrame(byte[] data, Camera camera);
};
複製代碼
拍照的那一刻回調bash
@Deprecated
public interface ShutterCallback{
void onShutter();
}
複製代碼
拍照後回調--data即是圖片數據微信
@Deprecated
public interface PictureCallback {
void onPictureTaken(byte[] data, Camera camera);
};
複製代碼
自動聚焦監聽ide
@Deprecated
public interface AutoFocusCallback{
void onAutoFocus(boolean success, Camera camera);
}
複製代碼
takePicture
Camera open() 打開一個Camera(生成對象)
void startPreview() 開啓預覽
void stopPreview() 關閉預覽
void release() 釋放資源
void autoFocus(AutoFocusCallback cb) 自動聚焦
複製代碼
takePicture
---->[四參的:takePicture]-------------
* @param shutter 拍照瞬間回調
* @param raw 回調未壓縮的原始數據
* @param postview 回調與postview圖像數據
* @param jpeg 回調JPEG圖片數據
*/
public final void takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback postview, PictureCallback jpeg) {
---->[三參的:takePicture,第三參null]-------------
public final void takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback jpeg) {
takePicture(shutter, raw, null, jpeg);
}
複製代碼
拍完照要
camera.startPreview();
再開啓預覽, 不然界面就不動了
這裏測試拍照的文件名寫死了(避免拍太多測試照片...),你能夠用當前時間當文件名佈局
mIdIvSnap.setOnClickListener(v->{
camera.takePicture(new Camera.ShutterCallback() {
@Override
public void onShutter() {
}
}, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
}
}, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File pic = FileHelper.get().createFile("pic/hello.jpg");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(pic);
fos.write(data);
fos.flush();
fos.close();
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
try {
assert fos != null;
fos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
});
複製代碼
思路很簡單,就是用Handler發送延遲消息,將一個TextView先居中隱藏post
選中時拍照延遲3s(此處簡單地寫死,固然你也能夠暴漏設置方法)測試
private boolean isDelay = false;//是否延遲
mIdIvDelay.setOnClickListener(v -> {
if (!isDelay) {
mIdIvDelay.setImageTintList(ColorStateList.valueOf(0xffEFB90F));
} else {
mIdIvDelay.setImageTintList(ColorStateList.valueOf(0xfffffffF));
}
isDelay = !isDelay;
});
複製代碼
private static final int DEFAULT_DELAY_COUNT = 3 + 1;//默認延遲時間3s
private int mCurDelayCount = DEFAULT_DELAY_COUNT;//當前倒計時時間
private Handler mHandler = new Handler() {//Handler
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mIdTvCountDown.setText(mCurDelayCount + "");
}
};
//點擊拍照按鈕
mIdIvSnap.setOnClickListener(v -> {
if (!isDelay) {//若是無延遲直接拍
takePicture("pic/hello.jpg");
return;
}
mIdTvCountDown.setVisibility(View.VISIBLE);
mCurDelayCount = DEFAULT_DELAY_COUNT;
mHandler.post(new Runnable() {
@Override
public void run() {
if (mCurDelayCount > 0) {
mCurDelayCount--;
L.d(mCurDelayCount + L.l());
mHandler.postDelayed(this, 1000);//延遲1s
mHandler.sendEmptyMessage(0);//發送消息
} else {
takePicture("pic/hello.jpg");
mIdTvCountDown.setVisibility(View.GONE);
}
}
});
});
/**
* 拍照方法封裝
*
* @param name 圖片名稱(加文件夾:形式如:pic/hello.jpg)
*/
private void takePicture(String name) {
camera.takePicture(null, null, (data, camera) -> {
File pic = FileHelper.get().createFile(name);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(pic);
fos.write(data);
fos.flush();
fos.close();
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
try {
assert fos != null;
fos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
}
複製代碼
點擊SurfaceView自動聚焦(也就是變清楚)
//自動聚焦
mIdSvVideo.setOnClickListener(v -> {
camera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
}
});
});
複製代碼
我默認給了10個等級,放到最大以後回到開始大小
private int currZoom;//當前縮放數
mParameters = camera.getParameters();//相機參數
/**
* 縮放封裝
*/
public void setZoom() {
if (mParameters.isZoomSupported()) {//是否支持縮放
try {
Camera.Parameters params = mParameters;
final int maxZoom = params.getMaxZoom();
if (maxZoom == 0) return;
currZoom = params.getZoom();
currZoom += maxZoom / 10;
if (currZoom > maxZoom) {
currZoom = 0;
}
params.setZoom(currZoom);
camera.setParameters(params);
String rate = new DecimalFormat("#.0").format(currZoom / (maxZoom / 10 * 2.f) + 1);
mIdIvZoom.setText(rate + "x");
} catch (Exception e) {
e.printStackTrace();
}
} else {
ToastUtil.show(this, "您的手機不支持變焦功能!");
}
}
複製代碼
private boolean isFlashLight;//是否開啓閃光燈
//開閃光燈
mIdIvSplash.setOnClickListener(v -> {
if (!isFlashLight) {
mIdIvSplash.setImageTintList(ColorStateList.valueOf(0xffEFB90F));
} else {
mIdIvSplash.setImageTintList(ColorStateList.valueOf(0xfffffffF));
}
isFlashLight = !isFlashLight;
mParameters.setFlashMode(
isFlashLight?Camera.Parameters.FLASH_MODE_TORCH:Camera.Parameters.FLASH_MODE_OFF);
camera.setParameters(mParameters);
});
複製代碼
好吧,哥露臉了...
//切換鏡頭
mIdIvSwitch.setOnClickListener(v -> {
if (!isBack) {
mIdIvSwitch.setImageTintList(ColorStateList.valueOf(0xffEFB90F));
} else {
mIdIvSwitch.setImageTintList(ColorStateList.valueOf(0xfffffffF));
}
changeCamera(isBack ? BACK : FRONT);
isBack = !isBack;
});
private void changeCamera(int type) {
camera.stopPreview();
camera.release();
camera = openCamera(type);
try {
camera.setPreviewDisplay(mHolder);
camera.setDisplayOrientation(90);//並將展現方向旋轉90度--水平-->豎直
} catch (IOException e) {
e.printStackTrace();
}
camera.startPreview();
}
private Camera openCamera(int type) {
int frontIndex = -1;
int backIndex = -1;
int cameraCount = Camera.getNumberOfCameras();
Camera.CameraInfo info = new Camera.CameraInfo();
for (int cameraIndex = 0; cameraIndex < cameraCount; cameraIndex++) {
Camera.getCameraInfo(cameraIndex, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
frontIndex = cameraIndex;
} else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
backIndex = cameraIndex;
}
}
if (type == FRONT && frontIndex != -1) {
return Camera.open(frontIndex);
} else if (type == BACK && backIndex != -1) {
return Camera.open(backIndex);
}
return null;
}
複製代碼
Android 中Google支持的 PreviewCallback.onPreviewFrame的YUV經常使用格式有兩種:
一個是NV21,一個是YV12。Android通常默認使用YCbCr_420_SP的格式(NV21)。
拍照和錄像的切換,視頻下:變紅(偶數次點擊)時開始錄像,變藍(奇數次點擊)中止
private boolean isPhoto = true;//是不是拍照
private boolean isRecoding;//是否在錄像
private int clickRecordCount = 0;//錄屏時的點擊錄屏次數
//切換到錄製
mIdTvVideo.setOnClickListener(v -> {
mIdTvVideo.setTextColor(0xffEFB90F);
mIdTvPic.setTextColor(0xfffffffF);
mIdIvSnap.setImageTintList(ColorStateList.valueOf(0xff0FC2EF));
isPhoto = false;
});
//切換到拍照
mIdTvPic.setOnClickListener(v -> {
mIdTvVideo.setTextColor(0xfffffffF);
mIdIvSnap.setImageTintList(ColorStateList.valueOf(0x88ffffff));
mIdTvPic.setTextColor(0xffEFB90F);
isPhoto = true;
});
//開始按鈕
mIdIvSnap.setOnClickListener(v -> {
if (isPhoto) {
takePhoto();//照相
} else {
if (clickRecordCount % 2 == 0) {
recodeVideo();//錄製
} else {
stopRecodeVideo();//中止錄製
}
}
clickRecordCount++;
});
/**
* 錄像
*/
private void recodeVideo() {
isRecoding = true;
mIdIvSnap.setImageTintList(ColorStateList.valueOf(0xffff0000));
camera.startPreview();
}
/**
* 中止錄像
*/
private void stopRecodeVideo() {
isRecoding = false;
mIdIvSnap.setImageTintList(ColorStateList.valueOf(0xff0FC2EF));
}
//視頻錄像
camera.setPreviewCallback((data, camera) -> {
if (isRecoding) {
L.d("onPreviewFrame--" + Thread.currentThread().getName() + L.l());
//TODO 收集數據
}
}
);
複製代碼
拍張照都2M多,錄像還得了?隨便設了兩個尺寸沒效果...
Camera支持的尺寸是固定的哪幾種...
mParameters.setPictureSize(720, 480);//設置圖片尺寸
mParameters.setPreviewSize(720, 480);//設置預覽尺寸
複製代碼
//查看支持的尺寸
List<Camera.Size> pictureSizes = camera.getParameters().getSupportedPictureSizes();
List<Camera.Size> previewSizes = camera.getParameters().getSupportedPreviewSizes();
for (int i = 0; i < pictureSizes.size(); i++) {
Camera.Size pSize = pictureSizes.get(i);
L.d("PictureSize.width = " + pSize.width + "--------PictureSize.height = " + pSize.height);
}
for (int i = 0; i < previewSizes.size(); i++) {
Camera.Size pSize = previewSizes.get(i);
L.d("previewSize.width = " + pSize.width + "-------previewSize.height = " + pSize.height);
}
PictureSize.width = 5184--------PictureSize.height = 3880
PictureSize.width = 4608--------PictureSize.height = 3456
PictureSize.width = 4608--------PictureSize.height = 2592
PictureSize.width = 4608--------PictureSize.height = 2304
PictureSize.width = 4608--------PictureSize.height = 2176
PictureSize.width = 4608--------PictureSize.height = 2126
PictureSize.width = 4160--------PictureSize.height = 3120
PictureSize.width = 4160--------PictureSize.height = 2340
PictureSize.width = 4000--------PictureSize.height = 3000
PictureSize.width = 3840--------PictureSize.height = 2160
PictureSize.width = 3264--------PictureSize.height = 2448
PictureSize.width = 3264--------PictureSize.height = 1632
PictureSize.width = 3264--------PictureSize.height = 1552
PictureSize.width = 3264--------PictureSize.height = 1504
PictureSize.width = 3200--------PictureSize.height = 2400
PictureSize.width = 2592--------PictureSize.height = 1944
PictureSize.width = 2592--------PictureSize.height = 1940
PictureSize.width = 2592--------PictureSize.height = 1296
PictureSize.width = 2592--------PictureSize.height = 1232
PictureSize.width = 2592--------PictureSize.height = 1458
PictureSize.width = 2560--------PictureSize.height = 1920
PictureSize.width = 2688--------PictureSize.height = 1512
PictureSize.width = 2304--------PictureSize.height = 1728
PictureSize.width = 2304--------PictureSize.height = 1296
PictureSize.width = 2048--------PictureSize.height = 1536
PictureSize.width = 1920--------PictureSize.height = 1080
PictureSize.width = 1840--------PictureSize.height = 1380
PictureSize.width = 1600--------PictureSize.height = 1200
PictureSize.width = 1600--------PictureSize.height = 900
PictureSize.width = 1440--------PictureSize.height = 1080
PictureSize.width = 1280--------PictureSize.height = 960
PictureSize.width = 1280--------PictureSize.height = 768
PictureSize.width = 1280--------PictureSize.height = 720
PictureSize.width = 1024--------PictureSize.height = 768
PictureSize.width = 800--------PictureSize.height = 600
PictureSize.width = 800--------PictureSize.height = 480
PictureSize.width = 720--------PictureSize.height = 480
PictureSize.width = 640--------PictureSize.height = 480
PictureSize.width = 352--------PictureSize.height = 288
PictureSize.width = 320--------PictureSize.height = 240
PictureSize.width = 176--------PictureSize.height = 144
previewSize.width = 2160-------previewSize.height = 1080
previewSize.width = 1920-------previewSize.height = 1080
previewSize.width = 1600-------previewSize.height = 900
previewSize.width = 1520-------previewSize.height = 720
previewSize.width = 1440-------previewSize.height = 1080
previewSize.width = 1280-------previewSize.height = 960
previewSize.width = 1280-------previewSize.height = 720
previewSize.width = 960-------previewSize.height = 720
previewSize.width = 720-------previewSize.height = 480
previewSize.width = 640-------previewSize.height = 480
previewSize.width = 352-------previewSize.height = 288
previewSize.width = 320-------previewSize.height = 240
previewSize.width = 176-------previewSize.height = 144
複製代碼
獲取的數據暫時還沒法解析,先留着吧
//視頻錄像
camera.setPreviewCallback((data, camera) -> {
if (isRecoding) {
collectData(data);
}
}
);
/**
* 收集數據
*
* @param data
*/
private void collectData(byte[] data) {
try {
mFosVideo.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 錄像時生成流mFosVideo
*/
private void recodeVideo() {
isRecoding = true;
File videoFile = FileHelper.get().createFile("video/hello");
try {
mFosVideo = new FileOutputStream(videoFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
mIdIvSnap.setImageTintList(ColorStateList.valueOf(0xffff0000));
camera.startPreview();
}
/**
* 中止錄像關閉流
*/
private void stopRecodeVideo() {
isRecoding = false;
mIdIvSnap.setImageTintList(ColorStateList.valueOf(0xff0FC2EF));
try {
mFosVideo.flush();
mFosVideo.close();
} catch (IOException e) {
e.printStackTrace();
}
}
複製代碼
Camera+MediaRecorder
MediaRecorder不止能錄音頻,結合Camera還能錄視頻
videoSize.width = 2160-------videoSize.height = 1080
videoSize.width = 1920-------videoSize.height = 1080
videoSize.width = 1280-------videoSize.height = 960
videoSize.width = 1440-------videoSize.height = 720
videoSize.width = 1280-------videoSize.height = 720
videoSize.width = 864-------videoSize.height = 480
videoSize.width = 800-------videoSize.height = 480
videoSize.width = 720-------videoSize.height = 480
videoSize.width = 640-------videoSize.height = 480
videoSize.width = 352-------videoSize.height = 288
videoSize.width = 320-------videoSize.height = 240
videoSize.width = 176-------videoSize.height = 144
複製代碼
/**
* 做者:張風捷特烈<br/>
* 時間:2019/1/8 0008:16:29<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:視頻錄製輔助類
*/
public class VideoRecorderUtils {
private MediaRecorder mediaRecorder;
private Camera camera;
private SurfaceHolder.Callback callback;
private SurfaceView surfaceView;
private int height, width;
public static Point WH_2160X1080 = new Point(2160, 1080);
public static Point WH_1920X1080 = new Point(1920, 1080);
public static Point WH_1280X960 = new Point(1280, 960);
public static Point WH_1440X720 = new Point(1440, 720);
public static Point WH_1280X720 = new Point(1280, 720);
public static Point WH_864X480 = new Point(864, 480);
public static Point WH_800X480 = new Point(800, 480);
public static Point WH_720X480 = new Point(720, 480);
public static Point WH_640X480 = new Point(640, 480);
public static Point WH_352X288 = new Point(352, 288);
public static Point WH_320X240 = new Point(320, 240);
public static Point WH_176X144 = new Point(176, 144);
public void create(SurfaceView surfaceView,Point point) {
this.surfaceView = surfaceView;
surfaceView.setKeepScreenOn(true);
callback = new SurfaceHolder.Callback() {
public void surfaceCreated(SurfaceHolder holder) {
camera = Camera.open();
width = point.x;
height = point.y;
mediaRecorder = new MediaRecorder();
}
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
doChange(holder);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (camera != null) {
camera.release();
camera = null;
}
}
};
surfaceView.getHolder().addCallback(callback);
}
private void doChange(SurfaceHolder holder) {
try {
camera.setPreviewDisplay(holder);
camera.setDisplayOrientation(90);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
public void stopRecord() {
mediaRecorder.release();
camera.release();
mediaRecorder = null;
camera = Camera.open();
mediaRecorder = new MediaRecorder();
doChange(surfaceView.getHolder());
}
public void stop() {
if (mediaRecorder != null && camera != null) {
mediaRecorder.release();
camera.release();
}
}
public void destroy() {
if (mediaRecorder != null && camera != null) {
mediaRecorder.release();
camera.release();
mediaRecorder = null;
camera = null;
}
}
/**
* @param path 保存的路徑
* @param name 錄像視頻名稱(不包含後綴)
*/
public void startRecord(String path, String name) {
camera.unlock();
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setVideoEncodingBitRate(700 * 1024);
mediaRecorder.setVideoSize(width, height);
mediaRecorder.setVideoFrameRate(24);
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
mediaRecorder.setOutputFile(path + File.separator + name + ".mp4");
File file1 = new File(path + File.separator + name + ".mp4");
if (file1.exists()) {
file1.delete();
}
mediaRecorder.setPreviewDisplay(surfaceView.getHolder().getSurface()
mediaRecorder.setOrientationHint(0);
try {
mediaRecorder.prepare();
mediaRecorder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
避免看起來雜亂,新建了一個Activity類 使用的核心方法:
private boolean isRecording;
mVideoRecorderUtils = new VideoRecorderUtils();
mVideoRecorderUtils.create(mIdSvVideo, VideoRecorderUtils.WH_720X480);
path = Environment.getExternalStorageDirectory().getAbsolutePath();
mIdIvSnap.setOnClickListener(view -> {
if (!isRecording) {
mVideoRecorderUtils.startRecord(path, "Video");
} else {
mVideoRecorderUtils.stopRecord();
}
isRecording = !isRecording;
});
複製代碼
OK,就這樣,還有寫Camera的特效,等之後把圖片知識弄好,再說吧
項目源碼 | 日期 | 備註 |
---|---|---|
V0.1-github | 2018-1-8 | Android多媒體之Camera的相關操做](https://www.jianshu.com/p/6db677f9dc9e) |
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
個人github | 個人簡書 | 個人掘金 | 我的網站 |
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持