編輯:關於android開發



最近在做一個3D圖片采集與展示。
主要功能為:自定義Camera(google 已經擯棄了Camera, 推薦使用Camera2,後續篇幅,我將會用Camera2取代Camera),圍繞一個物體360度錄制一個視頻,然後在該視頻抽取一定數量的幀,保存為圖片存放。最後在一個Activity頁面展示第一張圖片,通過滑動或點擊切換下一張圖片,從而形成用圖片展示的3D效果。該項目主要的目的是采集3D圖片素材,然後上傳到服務器處理,最終在用戶客戶端或網頁端展示是通過OpenGL ES處理而來。
技術要點:
mCamera.getParameters().getSupportedPreviewSizes();
mCamera.getParameters().getSupportedPictureSizes();
可以通過debug查看該攝像頭支持哪些預覽框大小、圖片大小。如果要做適配,需要通過輪詢獲取自己所需要范圍大小。通常情況下,是支持全屏大小,也就是該手機的像素尺寸。
如果你想把預覽框設置成正方形,原則上是不行的(本人目前沒有找到相應的方法,求大牛指示),那我們可以先用全屏的攝像框尺寸,然後通過在層疊隱藏部分區域形成正方形,
再獲取圖像時根據隱藏的區域做相應的截圖,就能得到你想要的任何效果了。(如上圖示例)
在預覽中也有些小問題,比如說Camera默認是橫向取景,你需要 mCamera.setDisplayOrientation(90); 同時圖片保存的時候需要再旋轉90度,才能達到預覽的效果。
CamcorderProfile mProfile = null;
if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) {
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
}else if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)){
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
}else {
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
}
主要還是要根據自己的實際情況去獲取,然後再設置參數:
mMediaRecorder.setProfile(mProfile);//不知道為什麼,直接這麼設置,不起作用,要下面set 才行
//mMediaRecorder.setProfile(mProfile);//不知道為什麼,直接這麼設置,不起作用,要下面set 才行
mMediaRecorder.setOutputFormat(mProfile.fileFormat);// 視頻輸出格式
mMediaRecorder.setVideoFrameRate(mProfile.fileFormat);// 設置錄制的視頻幀率
mMediaRecorder.setVideoSize(mHeight, mWidth);;// 設置分辨率:
mMediaRecorder.setVideoEncodingBitRate(mProfile.videoBitRate);// 設置幀頻率,然後就清晰了
mMediaRecorder.setVideoEncoder(mProfile.videoCodec);// 視頻錄制格式
MediaMetadataRetriever 類
getFrameAtTime(long timeUs)
通過時間去抽幀,而每一幀的時間也未必相隔是均勻的,這導致抽出來的幀會有重復,這需要再深入研究(後續再研究)。
具體方法為:
/**
* 獲取視頻關鍵幀
*/
public void getFrameFromVideo(String filePath, String dirName){
mFile = new File(filePath);
retriever = new MediaMetadataRetriever();
retriever.setDataSource(filePath);
fileLength = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
long lengthLong = Long.parseLong(fileLength) *1000/ (Constants.Num_Frame - 1);
String prefixName = Utils.randomCapital(5);//圖片名前綴
int k = 0;
for (long i = 0; i< Long.parseLong(fileLength)*1000+lengthLong; i=i+lengthLong){
k++;
Bitmap bitmap = retriever.getFrameAtTime(i);
if(bitmap != null){
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(90);//圖片默認是橫盤的,轉90度變豎屏
if(bitmap.getWidth() >bitmap.getHeight()){
bitmap = Bitmap.createBitmap(bitmap,(bitmap.getWidth() - bitmap.getHeight())/2,0, bitmap.getHeight(), bitmap.getHeight(),matrix, true);
}else{
bitmap = Bitmap.createBitmap(bitmap,0,(bitmap.getHeight()-bitmap.getWidth())/2, bitmap.getWidth(), bitmap.getWidth(),matrix, true);
}
BitmapUtils.saveBitmap(bitmap, dirName, prefixName + k + ".jpg");
}
if(k < 50){
sendMessageForProgress(dirName,k,true);
}else {
sendMessageForProgress(dirName,k,false);
}
}
//刪除視頻文件
mFile.delete();
}
最後呈現一下,預覽與錄制視頻的代碼吧。RecordVideoActivity
public class RecordVideoActivity extends AppCompatActivity implements View.OnClickListener
, SurfaceHolder.Callback, MediaRecorder.OnErrorListener{
private ProgressBar mProgressbar;
private SurfaceView mSurfaceView;
private MediaRecorder mMediaRecorder;// 錄制視頻的類
private SurfaceHolder mSurfaceHolder;
private Camera mCamera;
private Timer mTimer;// 計時器
private boolean isOpenCamera = true;// 是否一開始就打開攝像頭
private final static int mRecordMaxTime = 20;// 一次拍攝最長時間
private OnRecordFinishListener mOnRecordFinishListener;// 錄制完成回調接口
private int mTimeCount;// 時間計數
private File mVecordFile = null;// 文件
private int mWidth = 0;// 視頻分辨率寬度
private int mHeight = 0;// 視頻分辨率高度
private boolean isStarting = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉標題欄
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);// 設置全屏
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_record_video);
initView();
}
private void initView() {
mProgressbar = (ProgressBar) findViewById(R.id.progressBar);
mProgressbar.setMax(mRecordMaxTime);
findViewById(R.id.btn_start).setOnClickListener(this);
mSurfaceView = (SurfaceView)findViewById(R.id.surfaceView);
mSurfaceHolder = mSurfaceView.getHolder();// 取得holder
mSurfaceHolder.addCallback(this); // holder加入回調接口
mSurfaceHolder.setKeepScreenOn(true);
}
/**
* 初始化攝像頭
*/
private void initCamera(int width, int height) {
if (mCamera != null) {
freeCameraResource();
}
try {
mCamera = Camera.open();
if (mCamera == null)
return;
mCamera.setDisplayOrientation(90);//攝像頭默認是橫向,需要調整角度變成豎直
mCamera.setPreviewDisplay(mSurfaceHolder);
Camera.Parameters parameters = mCamera.getParameters();// 獲得相機參數
mWidth = width;
mHeight = height;
parameters.setPreviewSize(height, width); // 設置預覽圖像大小
parameters.set("orientation", "portrait");
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains("continuous-video")) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
mCamera.getParameters().getSupportedPreviewSizes();
mCamera.getParameters().getSupportedPictureSizes();
mCamera.setParameters(parameters);// 設置相機參數
mCamera.startPreview();// 開始預覽
mCamera.unlock();//解鎖,賦予錄像權限
} catch (Exception e) {
e.printStackTrace();
freeCameraResource();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stop();
}
/**
* 釋放攝像頭資源
*/
private void freeCameraResource() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.lock();
mCamera.release();
mCamera = null;
}
}
/**
* 錄制前,初始化
*/
private void initRecord() {
try {
mMediaRecorder = new MediaRecorder();
mMediaRecorder.reset();
if(mCamera != null)
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setOnErrorListener(this);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);// 視頻源
CamcorderProfile mProfile = null;
if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) {
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
}else if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)){
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
}else {
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
}
// mMediaRecorder.setProfile(mProfile);//不知道為什麼,直接這麼設置,不起作用,要下面set 才行
mMediaRecorder.setOutputFormat(mProfile.fileFormat);// 視頻輸出格式
mMediaRecorder.setVideoFrameRate(mProfile.fileFormat);// 設置錄制的視頻幀率
mMediaRecorder.setVideoSize(mHeight, mWidth);;// 設置分辨率:
mMediaRecorder.setVideoEncodingBitRate(mProfile.videoBitRate);// 設置幀頻率,然後就清晰了
mMediaRecorder.setVideoEncoder(mProfile.videoCodec);// 視頻錄制格式
// }
mMediaRecorder.setOutputFile(mVecordFile.getAbsolutePath());
mMediaRecorder.prepare();
mMediaRecorder.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 開始錄制視頻
*/
public void startRecord(final OnRecordFinishListener onRecordFinishListener) {
isStarting = true;
this.mOnRecordFinishListener = onRecordFinishListener;
createRecordDir();
try {
initRecord();
mTimeCount = 0;// 時間計數器重新賦值
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
mTimeCount++;
mProgressbar.setProgress(mTimeCount);// 設置進度條
if (mTimeCount == mRecordMaxTime) {// 達到指定時間,停止拍攝
stop();
if (mOnRecordFinishListener != null)
mOnRecordFinishListener.onRecordFinish();
}
}
}, 0, 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 停止拍攝
*/
public void stop() {
stopRecord();
releaseRecord();
freeCameraResource();
}
/**
* 停止錄制
*/
public void stopRecord() {
mProgressbar.setProgress(0);
if (mTimer != null)
mTimer.cancel();
if (mMediaRecorder != null) {
// 設置後不會崩
mMediaRecorder.setOnErrorListener(null);
mMediaRecorder.setPreviewDisplay(null);
try {
mMediaRecorder.stop();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 釋放資源
*/
private void releaseRecord() {
if (mMediaRecorder != null) {
mMediaRecorder.setOnErrorListener(null);
try {
mMediaRecorder.release();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
mMediaRecorder = null;
}
/**
* 創建目錄與文件
*/
private void createRecordDir() {
File FileDir = new File(Environment.getExternalStorageDirectory() + File.separator+"RecordVideo/");
if (!FileDir.exists()) {
FileDir.mkdirs();
}
// 創建文件
try {
mVecordFile = new File(FileDir.getAbsolutePath()+"/test.mp4");
Log.d("Path:", mVecordFile.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
OnRecordFinishListener recordFinishListener = new OnRecordFinishListener() {
@Override
public void onRecordFinish() {
Intent intent = new Intent(RecordVideoActivity.this, MainActivity.class);
intent.putExtra("filePath", mVecordFile.getAbsolutePath());
setResult(Activity.RESULT_OK, intent);
finish();
}
};
@Override
public void onBackPressed() {
if(!isStarting){
finish();
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_start:
if(!isStarting)
startRecord(recordFinishListener);
break;
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
initCamera(width,height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
freeCameraResource();
}
@Override
public void onError(MediaRecorder mr, int what, int extra) {
try {
if (mr != null)
mr.reset();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 錄制完成回調接口
*/
public interface OnRecordFinishListener {
void onRecordFinish();
}
}
最後提供源代碼:https://github.com/xiaoxiaoqingyi/3DShow
美團Android DEX自動拆包及動態加載簡介
美團Android DEX自動拆包及動態加載簡介 概述 作為一個android開發者,在開發應用時,隨著業務規模發展到一定程度,不斷地加入新功能、添加新的類
【Android】常見問題解答,android
【Android】常見問題解答,android這裡匯總了用C#和VS2015開發Android App時一些常見的最基本的問題及解決辦法,以後有新的問題時都在這裡一並回答
用樹莓派實現對話機器人
用樹莓派實現對話機器人最近用樹莓派實現了一個能和人對話的機器人,簡要介紹一下。樹莓派(Raspberry Pi)是世界上最流行的微型電腦主板,是開源硬件的領導產品,它為學
快速自動更新Android Studio版本
快速自動更新Android Studio版本 快速自動更新Android Studio版本 在開發過程中,有些時候總是會報一些Android Studio vesio