Media Module之Camera(四) 拍照 上层分析

4.拍照

拍照的流程分为两个大部分,上层和底层。上层主要分析的是四种拍张方式:普通拍照、倒计时拍照、连拍和全景拍照;底层主要分析的是jni、native、hal和jpeg数据流。
先分析上层这块。

4.1 上层分析

4.1.1 普通拍照和倒计时拍照

关于拍照的主要操作是在PhotoModule.java中,主要流程如下图:


普通拍照

可以这样理解:所有的拍照都是倒计时拍照,普通拍照的倒计时时间是0而已;
如果是非0的倒计时拍照,首先在PhotoModule里面开启倒计时的线程,然后在PhotoUI里面对倒计时进行UI上的显示,最后在倒计时结束之后调用上面介绍的拍照流程。
下面简单看一下倒计时的有关操作:

@Override
    public void onShutterButtonClick() {
        .............
        .............
        .............
  /**倒计时拍照相关代码*/      
  String timer = mPreferences.getString(
  CameraSettings.KEY_TIMER, mActivity.getString(R.string.pref_camera_timer_default));//从sharedpreferences中获取倒计时长的值
        String sound = mPreferences.getString(
                CameraSettings.KEY_CAPTURESOUND_KEY,
mActivity.getString(R.string.pref_camera_capturesound_default));
        boolean isPlay = (sound.equals("on"));

        int seconds = Integer.parseInt(timer);
        // When shutter button is pressed, check whether the previous countdown is  finished. If not, cancel the previous countdown and start a new one.
        if (mUI.isCountingDown()) {
            mUI.cancelCountDown();
        }
        if (seconds > 0) {//如果倒计时的秒数大于0,则进行倒计时拍照
            String zsl = mPreferences.getString(CameraSettings.KEY_ZSL,
mActivity.getString(R.string.pref_camera_zsl_default));//获取zsl是否打开
            mUI.overrideSettings(CameraSettings.KEY_ZSL, zsl);
            mUI.startCountDown(seconds, isPlay);//开始倒计时拍照实现一些UI显示
        } else {//如果倒计时的秒数等于0,即普通拍照
            if(mFocusManager != null) {
                mSnapshotOnIdle = false;
                mFocusManager.doSnap();//
            }
        }
        mShutterPressing = false;
    }

其中ZSL (zero shutter lag) 中文名称为零延时拍照,是为了减少拍照延时,让拍照&回显瞬间完成的一种技术。用在普通拍照;

在线程中倒计时结束之后调用onCountDownFinished方法,PhotoModule.java实现了倒计时结束的监听

@Override
    public void onCountDownFinished() {
        mSnapshotOnIdle = false;
        mFocusManager.doSnap();
        mFocusManager.onShutterUp();
        mUI.overrideSettings(CameraSettings.KEY_ZSL, null);
    }

doSnap()方法经过进一步调用,其具体的拍照逻辑:

@Override
    public boolean capture() {
        ................
        ................
        ................
synchronized (mCameraDevice) {
           mParameters.setRotation(mJpegRotation);// 设置旋转角度信息
           CameraUtil.setGpsParameters(mParameters, loc);
        ................
        ................
        ................
        if (mCameraState != LONGSHOT) {
            mUI.enableShutter(false);
        }
        mCapturesound = mPreferences.getString(//获取设置中的拍照声音是否打开
                CameraSettings.KEY_CAPTURESOUND_KEY,
mActivity.getString(R.string.pref_camera_capturesound_default));
        if (mCapturesound.equals("on")){
            mCameraDevice.enableShutterSound(true);//如果打开,则拍照的时候可以启动声音
        }else{
            mCameraDevice.enableShutterSound(false);
        }
if (mCameraState == LONGSHOT) {
            if(mLongshotSave) {
                mCameraDevice.takePicture(mHandler,
                        new LongshotShutterCallback(),
                        mRawPictureCallback, mPostViewPictureCallback,
                        new LongshotPictureCallback(loc));
            } else {
                mCameraDevice.takePicture(mHandler,
                        new LongshotShutterCallback(),
                        mRawPictureCallback, mPostViewPictureCallback,
                        new JpegPictureCallback(loc));
            }
        } else {
            mCameraDevice.takePicture(mHandler,
                    new ShutterCallback(!animateBefore),
                    mRawPictureCallback, mPostViewPictureCallback,
                    new JpegPictureCallback(loc));
            setCameraState(SNAPSHOT_IN_PROGRESS);
        }
        ................

保存图片的回调:

private final class JpegPictureCallback
            implements CameraPictureCallback {

        @Override
        public void onPictureTaken(final byte [] jpegData, CameraProxy camera) {
            if (mCameraState != LONGSHOT) {
                mUI.enableShutter(true);
            }            
            ....................
            ....................
            ....................
          System.out.println(33333);
          mActivity.getMediaSaveService().addImage(jpegData, title, date, mLocation, width, height,orientation, exif, mOnMediaSavedListener, mContentResolver, mPictureFormat);
          ....................

通过异步任务进入storage.java中的addImage方法中将图片保存到Media数据库中。

// Add the image to media store.
    public static Uri addImage(ContentResolver resolver, String title,
            long date, Location location, int orientation, int jpegLength,
            String path, int width, int height, String mimeType) {
        // Insert into MediaStore.
        ContentValues values =
                getContentValuesForData(title, date, location, orientation, jpegLength, path,
                        width, height, mimeType);

         return insertImage(resolver, values);
}

4.1.2 连拍

和普通拍照的区别就在于它在回调方法里面循环执行了onPictureTaken方法,直到达到限制的最大张数就停止连拍;

连拍流程图

具体的逻辑与上面的基本相同,不过有两点需要注意一下:1)连拍的间隔;2)连拍的张数限制。
对于连拍的间隔,只是在线程中一直循环执行,并没有直接设置中间间隔时长:

@Override
        public void onPictureTaken(
                final byte[] data, android.hardware.Camera camera) {
            final android.hardware.Camera currentCamera = mCamera.getCamera();
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (currentCamera != null && currentCamera.equals(mCamera.getCamera())) {
                        mCallback.onPictureTaken(data, mCamera);
                    }
                }
            });
        }

对于连拍张数限制问题,高通并未做任何张数限制,思路可以这样:记录每张照相动作的次数,设置最大张数的变量,分别在LongshotShutterCallback和JpegPictureCallback中对张数进行限制即可。

4.1.3 全景拍照

全景拍照
/*开始拍照*/
public void startCapture() {
// Reset values so we can do this again.
       .....................
       .....................
       .....................
mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {
            @Override
            public void onProgress(boolean isFinished, float panningRateX, float panningRateY,float progressX, float progressY) {

float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle;
float accumulatedVerticalAngle = progressY * mVerticalViewAngle;

boolean isRotated =!(mDeviceOrientationAtCapture == mDeviceOrientation);

if (isFinished
|| (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE)
|| (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)
|| isRotated) {
                    stopCapture(false);
                } else {
float panningRateXInDegree = panningRateX * mHorizontalViewAngle;
float panningRateYInDegree = panningRateY * mVerticalViewAngle;
if (mDeviceOrientation == 180 || mDeviceOrientation == 90) {
    accumulatedHorizontalAngle = -accumulatedHorizontalAngle;
    accumulatedVerticalAngle = -accumulatedVerticalAngle;
}                   
    mUI.updateCaptureProgress(panningRateXInDegree, panningRateYInDegree, accumulatedHorizontalAngle, accumulatedVerticalAngle, PANNING_SPEED_THRESHOLD);//更新拍照的进度条
                }
            }
        });
       .....................
       .....................
       .....................
    }
public void updateCaptureProgress(
            float panningRateXInDegree, float panningRateYInDegree,
            float progressHorizontalAngle, float progressVerticalAngle,
            float maxPanningSpeed) {
        if ((Math.abs(panningRateXInDegree) > maxPanningSpeed)
                || (Math.abs(panningRateYInDegree) > maxPanningSpeed)) {
            showTooFastIndication();//显示速度太快
        } else {
            hideTooFastIndication();//隐藏速度太快
        }

        // progressHorizontalAngle and progressVerticalAngle are relative to the
        // camera. Convert them to UI direction.
        mProgressAngle[0] = progressHorizontalAngle;
        mProgressAngle[1] = progressVerticalAngle;
        mProgressDirectionMatrix.mapPoints(mProgressAngle);

        int angleInMajorDirection =
                (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1]))
                        ? (int) mProgressAngle[0]
                        : (int) mProgressAngle[1];
        mCaptureProgressBar.setProgress((angleInMajorDirection));
    }
private void stopCapture(boolean aborted) {
        System.out.println(3333);
        .............
       .............
       .............
            runBackgroundThread(new Thread() {
                @Override
                public void run() {
                    MosaicJpeg jpeg = generateFinalMosaic(false);

                    if (jpeg != null && jpeg.isValid) {//若图片不为空,且存在
                        Bitmap bitmap = null;
                        bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length);
                        mMainHandler.sendMessage(mMainHandler.obtainMessage(
MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap));//保存图片
                    } else {
                        mMainHandler.sendMessage(mMainHandler.obtainMessage(
MSG_END_DIALOG_RESET_TO_PREVIEW));//回到预览界面
                    }
                }
            });
        }
        keepScreenOnAwhile();
    }
/*保存全景视图*/
    private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) {
        System.out.println(44444);
        if (jpegData != null) {
        ...........
        ...........
        ...........
            int jpegLength = (int) (new File(filepath).length());
            return Storage.addImage(mContentResolver, filename, mTimeTaken, loc, orientation,jpegLength, filepath, width, height, LocalData.MIME_TYPE_JPEG);//保存图片
        }
        return null;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,880评论 25 707
  • Java-Review-Note——4.多线程 标签: JavaStudy PS:本来是分开三篇的,后来想想还是整...
    coder_pig阅读 1,641评论 2 17
  • 我成长的环境中,包括我自己在内,我所接触的小伙伴基本都是独生子女,不论是男孩女孩,父母都是很疼爱的,所以说实话我并...
    白白的成长日记阅读 302评论 2 0
  • 持续更新,也欢迎Nodejs大牛进QQ群讨论:541925216参考网址:https://github.com/d...
    superchen阅读 6,194评论 0 0
  • 莫依偎我 我习于冷 志于成冰 莫依偎我 别走近我 我正升焰 万木俱焚 别走近我 来拥抱我 我自温馨 自全清凉 来拥...
    Yixie阅读 258评论 0 0