最近在做一个3D图片采集与展示。

主要功能为:自定义Camera(google 已经摈弃了Camera, 推荐使用Camera2,后续篇幅,我将会用Camera2取代Camera),围绕一个物体360度录制一个视频,然后在该视频抽取一定数量的帧,保存为图片存放。最后在一个Activity页面展示第一张图片,通过滑动或点击切换下一张图片,从而形成用图片展示的3D效果。该项目主要的目的是采集3D图片素材,然后上传到服务器处理,最终在用户客户端或网页端展示是通过OpenGL ES处理而来。

技术要点:

1.在SurfaceView 展示Camera的时候,如果不按照camera支持的尺寸比例,那么预览会出现拉伸。

mCamera.getParameters().getSupportedPreviewSizes();
mCamera.getParameters().getSupportedPictureSizes();

可以通过debug查看该摄像头支持哪些预览框大小、图片大小。如果要做适配,需要通过轮询获取自己所需要范围大小。通常情况下,是支持全屏大小,也就是该手机的像素尺寸。
如果你想把预览框设置成正方形,原则上是不行的(本人目前没有找到相应的方法,求大牛指示),那我们可以先用全屏的摄像框尺寸,然后通过在层叠隐藏部分区域形成正方形,
再获取图像时根据隐藏的区域做相应的截图,就能得到你想要的任何效果了。(如上图示例)

在预览中也有些小问题,比如说Camera默认是横向取景,你需要 mCamera.setDisplayOrientation(90); 同时图片保存的时候需要再旋转90度,才能达到预览的效果。

2.在录像时,也会遇到一些问题图片会有拉伸,跟预览的时候不一致。这时,应该获取设备所支持的参数。

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);// 视频录制格式

3. 从视频抽帧, 每一帧是bitmap形式返回。从Java接口提供的抽帧方法,

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

最新文章

  1. Page传回页面的值问题
  2. 7.arm汇编 bic和orr指令
  3. 从题目中学习java语法
  4. RabbitMQ (三) 发布/订阅 -摘自网络
  5. ssh通过密钥免密登录linux服务器
  6. PAT-乙级-1044. 火星数字(20)
  7. 1126. Magnetic Storms(单调队列)
  8. Android ListView动态更新数据
  9. The 500 Most Commonly Used Words in the English Language
  10. Bootstrap 前端框架 遇到的问题 解决方案
  11. layer.open()利用代码实现伪阻塞
  12. eclipse neon 发布
  13. IntelliJ IDEA部署tomcat时Edit Configuration Deployment无artifact选项
  14. JSON与JS对象的区别
  15. Swoft 图片上传与处理
  16. mysql 数据库 命令行的操作——对表和字段的操作
  17. 15.IEnumerable和IEnumerator
  18. WIN10护眼色
  19. URL基本结构
  20. Python学习笔记之装饰器原理

热门文章

  1. pch文件出现no such file or directory错误
  2. C#中的委托是什么?
  3. PartialView 加载Js
  4. amchart
  5. Python新手学习基础之函数-关键字参数
  6. android 数据存储的几种方式
  7. 深入理解7816(5)-----关于文件DF/EF/MF/FID/AID/SFI
  8. 转:linux执行shell脚本的方式及一些区别
  9. 使IE6同样支持圆角效果
  10. vim编辑器的设置文件