Camera开发系列之二 相机数据回调处理

章节

Camera开发系列之一-显示摄像头实时画面java

Camera开发系列之二-相机预览数据回调android

Camera开发系列之三-相机数据硬编码为h264git

Camera开发系列之四-使用MediaMuxer封装编码后的音视频到mp4容器github

Camera开发系列之五-使用MediaExtractor制做一个简易播放器缓存

Camera开发系列之六-使用mina框架实现视频推流框架

Camera开发系列之七-使用GLSurfaceviw绘制Camera预览画面 ide

本篇文章主要实现的功能以下:函数

  1. 拍照功能实现
  2. 录像功能实现
  3. 实时视频流回调

先上效果图:post

后置摄像头

拍照回调函数

相机拍照功能的实现主要依赖这两个方法:优化

void takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback postview, Camera.PictureCallback jpeg) void takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg) 复制代码

第二个方法等同于void takePicture( shutter, raw, null, jpeg),因此这里直接看第一个方法,先看第一个参数的官方文档解释:

Called as near as possible to the moment when a photo is captured from the sensor. This is a good opportunity to play a shutter sound or give other feedback of camera operation. This may be some time after the photo was triggered, but some time before the actual data is available.

大概就是能够在这个方法里进行拍照前的一些设置,好比播放快门声音之类的。

第二个参数的官方文档解释:

the callback for image capture moment, or null

在原始图像数据可用时触发,这里的原始数据是指未经处理的yuv数据,若是须要本身编码图片,可使用该回调获取数据。

第三个参数的官方文档解释:

callback with postview image data, may be null

postview图像数据的回调,不是全部硬件都支持这个,可能为空。

第四个参数的官方文档解释:

The jpeg callback occurs when the compressed image is available

JPEG图像数据的回调,通过android底层处理好的数据,可能为空。

拍照并保存为jpeg

这里我就直接拿到jpeg数据作存储操做了,这里须要注意的是照片数据多是旋转过的,须要将照片旋转回来。

拍照并保存为jpeg格式的文件:

mCamera.takePicture(null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                File file = null;
                try {
                    if (mPicListener != null) {
                        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
                                data.length);
                        //由于照片有多是旋转的,这里要作一下处理
                        Camera.CameraInfo info = new Camera.CameraInfo();
                        Camera.getCameraInfo(mCameraId, info);
                        Bitmap realBmp = FileUtil.rotaingBitmap(info.orientation, bitmap);

                        file = FileUtil.saveFile(realBmp, mFileName, mFileDir + "/");
                        mPicListener.onPictureTaken("", file);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    LogUtil.i("错误: " + e.getMessage());
                    if (mPicListener != null) {
                        mPicListener.onPictureTaken("保存失败:" + e.getMessage(), file);
                    }
                }
                mCamera.startPreview(); //拍照以后须要从新设置预览画面
            }
        });
复制代码

按角度旋转bitmap:

public static Bitmap rotaingBitmap(int angle, Bitmap bitmap) {
        Bitmap returnBm = null;
        // 根据旋转角度,生成旋转矩阵
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        try {
            // 将原始图片按照旋转矩阵进行旋转,并获得新的图片
            returnBm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        } catch (OutOfMemoryError e) {
            e.printStackTrace();
        }
        if (returnBm == null) {
            returnBm = bitmap;
        }
        if (bitmap != returnBm && !bitmap.isRecycled()) {
            bitmap.recycle();
            bitmap = null;
        }
        return returnBm;
    }
复制代码

嗯...好像是那么回事儿了,赶忙拍张照压压惊:

后置摄像头

恩,很完美,看看前置的摄像头拍的效果如何:

前置摄像头

仍是很完美,有人就会说我在这儿胡扯了,你这拍出来的效果根本就不对好伐!没看见文字都不同吗??

30米的大刀

兄dei别激动,先放下你手里的刀,听我慢慢跟你解释清楚,解释不清楚你再动手也不迟:

通常前置摄像头有270度的旋转,并且作了镜像翻转。镜像翻转指的是将屏幕进行水平的翻转,达到全部内容显示都会反向的效果,就像是在镜子中看到的界面同样。若是不想要这样的效果,能够拿到拍照的原始数据进行旋转270度和镜像翻转:

private byte[] rotateYUVDegree270AndMirror(byte[] data, int imageWidth, int imageHeight) {
        byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
        // Rotate and mirror the Y luma
        int i = 0;
        int maxY = 0;
        for (int x = imageWidth - 1; x >= 0; x--) {
            maxY = imageWidth * (imageHeight - 1) + x * 2;
            for (int y = 0; y < imageHeight; y++) {
                yuv[i] = data[maxY - (y * imageWidth + x)];
                i++;
            }
        }
        // Rotate and mirror the U and V color components
        int uvSize = imageWidth * imageHeight;
        i = uvSize;
        int maxUV = 0;
        for (int x = imageWidth - 1; x > 0; x = x - 2) {
            maxUV = imageWidth * (imageHeight / 2 - 1) + x * 2 + uvSize;
            for (int y = 0; y < imageHeight / 2; y++) {
                yuv[i] = data[maxUV - 2 - (y * imageWidth + x - 1)];
                i++;
                yuv[i] = data[maxUV - (y * imageWidth + x)];
                i++;
            }
        }
        return yuv;
    }
复制代码

录像功能实现

首先不要忘记添加录音权限:

<uses-permission android:name="android.permission.RECORD_AUDIO"/>
复制代码

这里录像功能的实现我依赖于android自带的MediaRecorder类,MediaRecorder是Android系统自带的一种很是强大的音频录制的控件,能够录制声音,也能够经过调用Camera达到录制视频的效果。MediaRecorder包含了Audio和video的记录功能,在Android的系统里,Music和Video两个应用程序都是调用MediaRecorder实现的。

本篇文章主要讲camer,对于的一些具体实现和方法就不在这儿具体赘述,有想了解的同窗能够看这篇文章:Android系统的录音功能MediaRecorder,我这里就直接上代码了:

public boolean initRecorder(String filePath, SurfaceHolder holder) {

        if (!mInitCameraResult) {
            LogUtil.i("相机未初始化成功");
            return false;
        }
        try {
            // TODO init button
            //mCamera.stopPreview();
            mediaRecorder = new MediaRecorder();
            mCamera.unlock();
            mediaRecorder.setCamera(mCamera);
            if (mCameraId == 1) {
                mediaRecorder.setOrientationHint(270);
            } else {
                mediaRecorder.setOrientationHint(90);
            }

            // 这两项须要放在setOutputFormat以前,设置音频和视频的来源
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);//摄录像机
            mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//相机

            // 设置录制完成后视频的封装格式THREE_GPP为3gp.MPEG_4为mp4
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            //这两项须要放在setOutputFormat以后 设置编码器
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            // 设置录制的视频编码h263 h264
            mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            // 设置视频录制的分辨率。必须放在设置编码和格式的后面,不然报错
            mediaRecorder.setVideoSize(mWidth, mHeight);
            // 设置视频的比特率 (清晰度)
            mediaRecorder.setVideoEncodingBitRate(3 * 1024 * 1024);
            // 设置录制的视频帧率。必须放在设置编码和格式的后面,不然报错
            /*if (defaultVideoFrameRate != -1) { mediaRecorder.setVideoFrameRate(defaultVideoFrameRate); }*/
            // 设置视频文件输出的路径 .mp4
            mediaRecorder.setOutputFile(filePath);
            mediaRecorder.setMaxDuration(30000);
            mediaRecorder.setPreviewDisplay(holder.getSurface());
            mediaRecorder.prepare();
            mediaRecorder.start();  //开始
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
复制代码

这里要注意的一个地方是上面备注提到的顺序!顺序!顺序!重要的事儿说三遍,顺序千万不能乱!若是顺序不对,可能会出现没法调用start()方法或者调用start()后闪退的状况。

相机实时视频流回调

和拍照函数相似的,主要看下面两个方法:

setPreviewCallback(Camera.PreviewCallback cb)
setPreviewCallbackWithBuffer(Camera.PreviewCallback cb)
复制代码

看看官方文档的解释:

对第一个方法:

Installs a callback to be invoked for every preview frame in addition to displaying them on the screen. The callback will be repeatedly called for as long as preview is active. This method can be called at any time, even while preview is live. Any other preview callbacks are overridden.

除了在屏幕上显示预览以外,还增长一个回调函数,在每一帧出现时调用。只要预览处于活动状态,就会重复调用回调。这种方法能够随时调用,保证预览是实时的。

第二个方法:

Installs a callback to be invoked for every preview frame, using buffers supplied with addCallbackBuffer(byte[]), in addition to displaying them on the screen.

在摄像头开启时增长一个回调函数,在每一帧出现时调用.经过addCallbackBuffer(byte[])使用一个缓存容器来显示这些数据.

这里推荐使用第二个方法,第二个方法其实就是经过内存复用来提升预览的效率,可是若是没有调用这个方法addCallbackBuffer(byte[]),帧回调函数就不会被调用,也就是说在每一次回调函数调用后都必须调用addCallbackBuffer(byte[])。(因此能够直接在onPreviewFrame中调用addCallbackBuffer(byte[]),即camera.addCallbackBuffer(data);),复用这个原来的内存地址便可。是否是听懵了?不要紧,直接看代码更容易理解:

//1.设置回调:系统相机某些核心部分不走JVM,进行特殊优化,因此效率很高
        mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
            @Override
            public void onPreviewFrame(byte[] datas, Camera camera) {
                //回收缓存处理
                camera.addCallbackBuffer(datas);
                
            }
        });
        //2.增长缓冲区buffer: 这里指定的是yuv420sp格式
        mCamera.addCallbackBuffer(new byte[((width * height) *
                ImageFormat.getBitsPerPixel(ImageFormat.NV21)) / 8]);
复制代码

onPreviewFrame这个回调函数是在Camera.open(int)从中调用的事件线程上调用的,它的第一个参数byte[] datas就是咱们须要的实时视频流数据。注意:若是Camera.Parameters.setPreviewFormat(int) 从未被调用,则datas数据默认为YCbCr_420_SP(NV21)格式。

视频流旋转角度

如今获得了相机的实时视频流,就能够进行编码封装格式保存了,不过须要注意的仍是旋转角度问题,这里要根据以前的旋转角度将视频流数据进行相应的旋转镜像操做,若是是前置摄像头,前面拍照已经给出解决方案,若是是后置摄像头,则可能须要旋转90度:

private byte[] rotateYUVDegree90(byte[] data, int imageWidth, int imageHeight) {
        byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
        // Rotate the Y luma
        int i = 0;
        for (int x = 0; x < imageWidth; x++) {
            for (int y = imageHeight - 1; y >= 0; y--) {
                yuv[i] = data[y * imageWidth + x];
                i++;
            }
        }
        // Rotate the U and V color components
        i = imageWidth * imageHeight * 3 / 2 - 1;
        for (int x = imageWidth - 1; x > 0; x = x - 2) {
            for (int y = 0; y < imageHeight / 2; y++) {
                yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
                i--;
                yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x - 1)];
                i--;
            }
        }
        return yuv;
    }
复制代码

关于相机开发,最坑的地方仍是旋转角度的问题,不一样手机可能有不一样的旋转角度。鉴于本章篇幅有限,下篇再介绍如何利用Android自带的编码类Mediacodec硬编码yuv数据为h264。博主刚开始写博客,文字表达能力不是很好,若是有表述不清或者错误的地方,欢迎你们指出==

参考文章:

分享几个Android摄像头采集的YUV数据旋转与镜像翻转的方法

Android系统自带的MediaRecorder结合Camera实现视频录制及播放功能

项目地址:camera开发从入门到入土 欢迎start和fork

相关文章
相关标签/搜索