辛苦堆砌,转载请注明出处,谢谢大家的支持。
前段时间工作很忙,忙了也好,开始思考自己到底应该如何工作,如何生活。前段时间的项目主要围绕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 三个接口,合理配置相机的接口调用顺序即可。
下一篇文章将基于当前版本做一些重构,然后实现自动拍照和定时连拍。
源码已上传到 这里