第24回 嗯?现在照相都不用胶卷啦?

来源:互联网 发布:设数组array由以下 编辑:程序博客网 时间:2024/05/01 00:21

这一天,刘关张三人正在用功编程,忽然孔明闯进屋来,“好消息啊主公!”

刘备抬起眼看看孔明,喜道:“军师何事如此激动?莫非村前烧饼铺今天买二送一?”

孔明:“靠,哪跟哪啊。我听闻大蜀科技公司被曹操的山寨抄袭策略逼得无路可走,总裁刘璋心理防线完全崩溃。此时正是我们收购他们公司的大好时机呀,有了这家公司,我们就可以与曹贼相抗衡啦!”

刘备:“军师说得轻巧,收购?我还想收购微软呢,你给我钱啊?”

孔明:“听说现在刘璋正以白菜价格抛售公司股份。我们把这草屋卖了,得来的钱应该足以进行收购!”

刘备:“那还等什么?事不宜迟,赶紧上赤兔!”

于是四个人骑着赤兔晃晃悠悠地来到了成都,话说这赤兔跑得快不快倒没看出来,但确实是挺能驼的。

刘璋听说居然还有人来收购他的公司,激动的鞋都没穿就跑了出来,见到刘备之后一把鼻涕一把泪,抹得刘备一脸。

在清点大蜀科技公司的资产之后发现,原来刘璋的公司只剩下办公室,两三台服务器,还有几个老弱病残的技术人员,核心开发者都被曹操挖了去,难怪刘璋会以白菜价抛售自己的公司。

刘璋叹息道:“本来公司正在研发一个拍照社交类产品,结果技术人员被曹操挖的挖,项目也进行不下去了,这里有很多半成品的代码,看你们是继续开发还是另做其他项目。唉,我是不行了,回家种种白菜啥的,大家多保重!”

众人送刘璋走后,张飞问孔明:“军师,刚才刘璋在这我没好意思问,现在照相都不用胶卷啦?”

1.1. 使用照相机

Android移动终端大多带有摄像头,提供拍照及摄像的功能。那么如何在自己的应用程序当中嵌入拍照功能呢?事实上,Android提供了两种方法用于调用摄像头:一种是启动一个Action,直接调用系统默认的拍照页面;而另一种是自定义SurfaceView类进行拍照。第一种方法的优点在于简单、方便,缺点是需要使用系统默认页面,不能进行修改,也不方便进行照相机的参数设定。第二种方法相当于利用系统提供的相机接口,自定义一个拍照页面,这样可以自由安排照相页面的布局并对照相机参数功能进行设定。

1.1.1.系统照相机

当需要进行拍照任务时,可以直接调用系统相机进行拍照。调用的原理是通过Intent启动系统相机,并传入拍照后的照片的存储路径参数。在利用系统相机进行拍照之后,照片会存储在指定的路径之下。返回应用界面后,可以读取该路径下的图片文件进行图片处理。这样做的优点在于简单、便捷,由于使用系统默认的相机界面,功能强大而且全面。但是缺点在于缺乏灵活性,不能自定义照片的参数和拍摄方式。如果对拍摄页面没有个性化需求的话,建议使用这种方式进行拍照功能开发,可以通过较少工作量获得较好的拍摄效果。

下面通过一个示例介绍如何直接调用系统相机:

调用系统相机代码代码清单24-1-1:

private voiddoTakePhoto() {ivatected何直接调用系统的需求的话,建议使用这种开发方式

           if (!PHOTO_DIR.exists()) {

// 创建照片的存储目录,PHOTO_DIR为指定的文件夹路径

                     boolean iscreat =PHOTO_DIR.mkdirs();

           }

           //将当前时间作为图片的名字

           FileName = System.currentTimeMillis()+ ".jpg";

           mCurrentPhotoFile = newFile(PHOTO_DIR, FileName);

           final Intent intent =getTakePickIntent(mCurrentPhotoFile);

           startActivityForResult(intent,CAMERA_WITH_DATA);

}

public staticIntent getTakePickIntent(File f) {

           //设置调用系统相机的Intent

           Intent intent = newIntent(MediaStore.ACTION_IMAGE_CAPTURE, null);

           //设置照片存储路径

           intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(f));

           return intent;

}

           上述代码包含了两个方法:

l  doTakePhoto():设置照片的存储路径,并以当前时间作为图片名称进行存储。将将要作为图片存储载体的文件mCurrentPhotoFile作为参数传递给getTakePickIntent()方法,通过Intent启动系统相机。

l  getTakePickIntent():设置系统相机Intent,返回结果是设置好的Intent。在该方法中通过intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(f))方法设置照片的存储路径。

1.1.2.自定义SurfaceView

虽然直接使用系统相机进行拍照非常的便捷,但是我们经常需要一些自定义的拍照环境和页面,这时就需要使用自定义SurfaceView来实现拍照功能。该方式的原理是以自定义的SurfaceView为承载容器,使用Android提供的拍照接口Camera类,并实现照片的回调方法PictureCallback(),最终实现照片的拍摄。

下面通过一个实例来演示如何自定义SurfaceView进行拍照。新建工程的布局文件main.xml,代码如下所示:

main.xml 代码清单24-1-2:

<!--刘备:请你以后不要在我面前说英文了,OK?-->

<?xmlversion="1.0" encoding="utf-8"?>

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"

   android:layout_width="match_parent"

   android:layout_height="match_parent"

    android:background="#A9A9A9"

    android:orientation="vertical">

<RelativeLayout

        android:id="@+id/main_photo_rl"

        android:layout_width="500dp"

        android:layout_height="300dp"

       android:layout_centerInParent="true"

        android:padding="3dp"

       android:background="#B0E0E6"  >

<SurfaceView

                    android:id="@+id/photo_surface_view"

                     android:layout_width="fill_parent"

        android:layout_height="fill_parent"

        android:layout_centerInParent="true"/>

</RelativeLayout>

<Button

       android:id="@+id/take_photo_btn"

       android:layout_width="wrap_content"

       android:layout_height="wrap_content"

       android:layout_marginTop="180dp"

       android:layout_toRightOf="@id/main_photo_rl"

        android:text="拍照" />

</RelativeLayout>

这布局文件定义了SurfaceView,用于承载相机镜头,旁边有一个按钮供用户点击进行拍照。新建一个Activity,命名为MainActivity,代码如下所示:

MainActivity.java 代码清单24-1-2:

/**

* @author刘备:我这人从不记仇,一般有仇当场我就报了。

 */

public classMainActivity extends Activity implements Callback, OnClickListener {

    // 用于承载Camera的SurfaceView

    SurfaceView mySurfaceView;

    // 用于捕获拍摄照片的SurfaceHolder类

    SurfaceHolder holder;

    // Camera类

    Camera myCamera;

    // 将照片存储在SD卡当中

    String mFilePath ="/sdcard/TmpPic.jpg";

         privateButton mCaptrueButton;

    Bitmap bm;

    // 拍摄照片后的回调方法

    PictureCallback jpeg = newPictureCallback() {

        @Override

        public void onPictureTaken(byte[] data,Camera camera) {

            try {

                bm =BitmapFactory.decodeByteArray(data, 0, data.length);

                File file = newFile(mFilePath);

                BufferedOutputStreambos =

 new BufferedOutputStream(newFileOutputStream(file));

               bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);

// 将照片存储为JPEG格式,质量为100

                bos.flush();

                bos.close();

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    };

    @Override

    public void onCreate(BundlesavedInstanceState) {

        super.onCreate(savedInstanceState);

        // 设定界面风格为无Title型

        requestWindowFeature(Window.FEATURE_NO_TITLE);

       this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        setContentView(R.layout.main);

        // 初始化SurfaceView

        mySurfaceView =(SurfaceView)findViewById(R.id.photo_surface_view);

        // 获得该SurfaceView的SurfaceHolder类

        holder = mySurfaceView.getHolder();

        // 添加照片回调方法

        holder.addCallback(this);

                     holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        // 初始化拍照按钮

        mCaptrueButton =(Button)findViewById(R.id.take_photo_btn);

        mCaptrueButton.setOnClickListener(this);

    }

    @Override

    public void surfaceChanged(SurfaceHolderholder, int format, int width, int height) {

        // 当拍照界面发生变化时调用

        Camera.Parameters params =myCamera.getParameters();

       params.setPictureFormat(PixelFormat.JPEG);

        myCamera.setParameters(params);

        // 由于启动时会调用该方法,所以在这里开启预览

        myCamera.startPreview();

    }

    @Override

    public void surfaceCreated(SurfaceHolderholder) {

        // 创建Camera

        if (myCamera == null) {

            myCamera = Camera.open();

            try {

               myCamera.setPreviewDisplay(holder);

            } catch (IOException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

        }

    }

    @Override

    public void surfaceDestroyed(SurfaceHolderholder) {

        // 关闭相机,释放资源

        myCamera.stopPreview();

        myCamera.release();

        myCamera = null;

    }

    @Override

    public void onClick(View v) {

        Camera.Parameters params =myCamera.getParameters();

       params.setPictureFormat(PixelFormat.JPEG);

        myCamera.setParameters(params);

        myCamera.takePicture(null, null, jpeg);

        mCaptrueButton.setClickable(false);

    }

}

上述代码声明了三个重要的类:SurfaceView类、SurfaceHolder类和Camera类。SurfaceView类属于View的子类,用于承载相机镜头的容器视图,照相机的镜头实际上在这个View中显示出来;SurfaceHolder是SurfaceView的一个操作类,可以通过SurfaceHolder添加照片回调方法,获得拍照所得的照片;Camera类是Android提供的照相接口,用于实现拍照功能。

在OnCreate()方法中,通过holder=mySurfaceView.getHolder()获得SurfaceView的SurfaceHolder,再通过holder.addCallback(this)方法添加SurfaceView的回调方法。由于该实例的MainActivity实现了Callback的接口,所以将this直接作为参数传递。

上述代码分别实现了surfaceChanged()、surfaceCreated()和surfaceDestroyed()三个Callback接口。由于surfaceChanged()方法会在SurfaceView启动时调用,所以在该方法中添加了myCamera.startPreview()方法用于启动相机预览。在surfaceCreated()、surfaceDestroyed()方法中添加了开启和关闭相机的逻辑。

值得注意的是,在完成上述三个接口的编写之后,需要调用PictureCallback()方法捕获照片,该方法将照片保存为JPEG格式并存储在SD卡中的指定路径下。

运行程序,结果如图24-1所示:

图24-1 运行结果

1.2. 使用摄像机

Android的摄像功能同照相功能相似,也是通过调用Camera接口控制摄像头进行拍摄,通过SurfaceView控件承载摄像机镜头。下面就通过一个录像实例介绍如何在Android上录制一段视频。

新建一个Activity,命名为RecordActivity,代码如下所示:

RecordActivity.java 代码清单24-2-0:

/**

* @author刘备:没什么事不要找我,有事更不用找我!

*/

public classRecordActivity extends Activity implements SurfaceHolder.Callback {

// 开始录制按钮

    private Button startButton;

// 停止录制按钮

    private Button stopButton;

    private Camera myCamera;

// 录制视频的类

    private MediaRecorder myMediaRecorder;

// 显示视频的控件

    private SurfaceView mSurfaceview;

    private SurfaceHolder mSurfaceHolder;

    public void onCreate(BundlesavedInstanceState) {

        super.onCreate(savedInstanceState);

                     // 调整界面风格为无Title

        requestWindowFeature(Window.FEATURE_NO_TITLE);

// 设置全屏

       getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

               WindowManager.LayoutParams.FLAG_FULLSCREEN);

       setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

       getWindow().setFormat(PixelFormat.RGB_888);

        setContentView(R.layout.main);

        init();

    }

// 初始化界面布局

    private void init() {

        startButton =(Button)this.findViewById(R.id.start);

        stopButton =(Button)this.findViewById(R.id.stop);

        startButton.setOnClickListener(newTestVideoListener());

        stopButton.setOnClickListener(newTestVideoListener());

        mSurfaceview =(SurfaceView)this.findViewById(R.id.surfaceview);

                     // 取得holder

        SurfaceHolder holder =mSurfaceview.getHolder();

// holder加入回调接口

        holder.addCallback(this);

       holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    }

           //释放MediaRecorder对象资源

    private void releaseMediaRecorder() {

        if (myMediaRecorder != null) {

            myMediaRecorder.stop();

            myMediaRecorder.reset();

            myMediaRecorder.release();

            myMediaRecorder = null;

            myCamera.lock();

        }

    }

    class TestVideoListener implementsOnClickListener {

        @Override

        public void onClick(View v) {

            if (v == startButton) {

                                          //点击开始录像按钮

                myCamera.stopPreview();

                myCamera.unlock();

// 创建mediarecorder对象

                myMediaRecorder = newMediaRecorder();

                // 设置录制视频源为Camera

               myMediaRecorder.setCamera(myCamera);

               myMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

                myMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);

                // 设置录制完成后视频的封装格式THREE_GPP

               myMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);

                // 设置录制的视频编码

               myMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

               myMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

                // 设置视频录制的分辨率。必须放在设置编码和格式的后面,否则报错

               myMediaRecorder.setVideoSize(352, 288);

                // 设置录制的视频帧率。必须放在设置编码和格式的后面,否则报错

               myMediaRecorder.setVideoFrameRate(15);

               myMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());

                // 设置视频文件输出的路径

               myMediaRecorder.setOutputFile("/sdcard/TmpVideo.3gp");

                try {

                    // 准备录制

                    myMediaRecorder.prepare();

                    // 开始录制

                    myMediaRecorder.start();

                } catch (IllegalStateExceptione) {

                    e.printStackTrace();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

            if (v == stopButton) {

                releaseMediaRecorder();

            }

        }

    }

    @Override

    public void surfaceChanged(SurfaceHolderholder, int format, int width, int height) {

        mSurfaceHolder = holder;

        try {

            myCamera.stopPreview();

        } catch (Exception e) {

        }

        try {

            myCamera.setPreviewDisplay(holder);

            Camera.Parameters mParameters =myCamera.getParameters();

                                //设置预览像素大小为352*288

            mParameters.setPreviewSize(352,288);

           myCamera.setParameters(mParameters);

            myCamera.startPreview();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

    @Override

    public void surfaceCreated(SurfaceHolderholder) {

        mSurfaceHolder = holder;

        if (null == myCamera)

            myCamera = Camera.open();

    }

    @Override

    public void surfaceDestroyed(SurfaceHolderholder) {

        // surface销毁时

        releaseMediaRecorder();

        myCamera.stopPreview();

        myCamera.release();

        myCamera = null;

    }

}

           上述代码和24.1.2小节使用SurfaceView进行照相的实例代码非常相似,该实例同样也重写了surfaceChanged()、surfaceCreated()和surfaceDestroyed()三个Callback接口,并在三个接口中实现了摄像头的开启预览、创建和关闭逻辑。

           与照相不同的是,在点击开始录制按钮之后,会创建一个录像控制类MediaRecorder用于拍摄视频并设置各种参数。通过MediaRecorder类的setCamera(myCamera)方法将摄像的Camera类与MediaRecord绑定在一起。还需要通过调用MediaRecorder类的其他方法设置视频格式、帧率、录制视频的存储路径等参数。值得注意的是,在停止录像时,需要释放MediaRecorder对象,否则会引起内存泄露。   

1.3.玄德有话说

张飞:大哥啊,照相开发蛮简单的嘛!

刘备:你不要被假象所迷惑!由于现在Android手机型号众多,各种照相机性能参差不齐,所以在开发时会遇到很多意想不到的问题,在开发完成之后也一定要进行尽可能广的机型兼容性测试。

张飞:啊?差点中了埋伏!那有什么好的解决方法吗?

刘备:如果对拍照功能的界面、功能、参数没什么限制的话还是建议使用直接调用系统相机的方式进行。这种方式最稳妥的,不会涉及到具体相机参数。如果进行自定义开发时设定照片的大小等参数,需要先取得支持的参数列表,再从中选取合适的进行设置。例如设置照片大小就需要通过params.getSupportedPictureSizes()方法获得相机支持的照片大小参数列表。

张飞:原来还有这么一招!

0 0
原创粉丝点击