1. 程式人生 > >Android Camera預設屬性設定

Android Camera預設屬性設定

需求說明:系統自帶Camera需要修改預設圖片預覽比例以及錄影比例。

android版本:8.1

Camera的啟動activity是CameraActivity.java

在啟動之後在oncreate裡開啟了許多初始化相關的程式碼。

    @Override
    public void onCreate(Bundle icicle) {

        super.onCreate(icicle);
        。。。。
        mCameraDeviceCtrl = new CameraDeviceCtrl(this, mPreferences);

        if (mPermissionManager.requestCameraLaunchPermissions()) {
            mCameraDeviceCtrl.openCamera();
        }
        。。。。
        ModuleCtrlImpl moduleCtrl = new ModuleCtrlImpl(this);
        CameraPerformanceTracker.onEvent(TAG,
                CameraPerformanceTracker.NAME_CAMERA_INIT_VIEW_MANAGER,
                CameraPerformanceTracker.ISBEGIN);
        mCameraAppUi = new CameraAppUiImpl(this);
        mCameraAppUi.createCommonView();
        initializeCommonManagers();
        mCameraAppUi.initializeCommonView();
        。。。
        setContentView(R.layout.camera);
        。。。
        parseIntent();
        CameraPerformanceTracker.onEvent(TAG,
                CameraPerformanceTracker.NAME_CAMERA_CREATE_MODULE,
                CameraPerformanceTracker.ISBEGIN);
        mModuleManager = new ModuleManager(this, fileSaver, mCameraAppUi,
                featureConfig, deviceManager, moduleCtrl, mISelfTimeManager);
        mISettingCtrl = mModuleManager.getSettingController();
        mCameraAppUi.setSettingCtrl(mISettingCtrl);

在setcontentview之前,建立了CameraDeviceCtrl,

在CameraDeviceCtrl的構造方法裡

    public CameraDeviceCtrl(CameraActivity activity, ComboPreferences preferences) {
        mCameraActivity = activity;
        mPreferences = preferences;
        mIsFirstStartUp = true;
        mMainHandler = new MainHandler(mCameraActivity.getMainLooper());
        mCameraStartUpThread = new CameraStartUpThread();
        mCameraStartUpThread.start();
        HandlerThread ht = new HandlerThread("Camera Handler Thread");
        ht.start();
        mCameraHandler = new CameraHandler(ht.getLooper());
    }

建立了CameraStartUpThread執行緒,並且啟動了它。

在CameraStartUpThread的run方法裡,就開始了各個引數的配置

@Override
        public void run() {
            while (mIsActive) {

                if (mIsFirstStartUp) {
                    CameraPerformanceTracker.onEvent(TAG,
                            CameraPerformanceTracker.NAME_CAMERA_START_UP,
                            CameraPerformanceTracker.ISBEGIN);
                    int openResult = firstOpenCamera();
                    if (CAMERA_OPEN_SUCEESS != openResult) {
                        setCameraState(CameraState.STATE_CAMERA_CLOSED);
                        mIsFirstStartUp = false;
                        continue;
                    }
                    ModeChecker.updateModeMatrix(mCameraActivity, mCameraId);
                    mCurCameraDevice.setDisplayOrientation(true);
                    mCurCameraDevice.setPreviewSize();
                    // if open camera too quickly, here may be run with
                    // setCameraActor at the same time, this will make CameraStartUpThread
                    // pause forever, so we synchronize it for workaround.
                    synchronized (mCameraActorSync) {
                        if (mCameraActor == null) {
                            try {
                                mCameraActorSync.wait();
                            } catch (InterruptedException e) {
                                Log.e(TAG, "mCameraActorSync.wait with InterruptedException");
                            }
                        }
                    }
                    mCameraActor.onCameraOpenDone();
                    mModuleManager.onCameraOpen();
                    initializeFocusManager();
                    if (mCameraActivity.getCurrentMode() == ModePicker.MODE_PHOTO) {
                        mCurCameraDevice.setPhotoModeParameters(
                                mCameraActivity.isNonePickIntent());
                    }
                    if (mCancel) {
                        Log.d(TAG, "[mIsFirstStartUp.run] cancel after openCamera");
                        mIsFirstStartUp = false;
                        continue;
                    }

                    if (mCameraActivity.isVideoCaptureIntent()) {
                        initializeSettingController();
                        mModuleManager.setModeSettingValue(
                                mCameraActor.getCameraModeType(mCameraActor.getMode()), "on");
                    }
                    applyFirstParameters();
                    //Block startUp thread when not set surface to native
                    mSycForLaunch.block(500);
                    mCurCameraDevice.setOneShotPreviewCallback(mOneShotPreviewCallback);
                    mMainHandler.sendEmptyMessage(MSG_CAMERA_PARAMETERS_READY);
                    mMainHandler.sendEmptyMessage(MSG_CAMERA_PREVIEW_DONE);
                    mMainHandler.sendEmptyMessage(MSG_CAMERA_OPEN_DONE);
                    Storage.mkFileDir(Storage.getFileDirectory());
                    clearDeviceCallbacks();
                    applyDeviceCallbacks();
                    mCameraAppUi.clearViewCallbacks();
                    mCameraAppUi.applayViewCallbacks();
                    if (mCancel) {
                        Log.d(TAG, "[mIsFirstStartUp.run]" +
                                " cancel before initializeSettingController");
                        mIsFirstStartUp = false;
                        continue;
                    }
                    if (!mCameraActivity.isVideoCaptureIntent()) {
                        initializeSettingController();
                        mModuleManager.setModeSettingValue(
                                mCameraActor.getCameraModeType(mCameraActor.getMode()), "on");
                    }
                    if (mCancel) {
                        Log.d(TAG, "[mIsFirstStartUp.run] cancel before applySecondParameters");
                        mIsFirstStartUp = false;
                        continue;
                    }
                    applySecondParameters();
                    setCameraState(CameraState.STATE_CAMERA_OPENED);
                    mIsFirstStartUp = false;
                    CameraPerformanceTracker.onEvent(TAG,
                            CameraPerformanceTracker.NAME_CAMERA_START_UP,
                            CameraPerformanceTracker.ISEND);
                    continue;
                }
                。。。。。

        }

這裡做了個判斷mIsFirstStartUp,接著走第一次啟動的時候的初始化,

這裡有兩個關鍵點:

mCurCameraDevice.setPreviewSize() 是設定預覽尺寸的地方,

initializeSettingController是進一步初始化其他設定屬性

第一步:先看看setPreviewsize做了什麼

可以發現,mCurCameraDevice是介面物件,而setPreviewSize是介面中的方法,找到是誰實現介面,發現

public interface ICameraDeviceExt
public class CameraDeviceExt implements ICameraDeviceExt

於是去看CameraDeviceExt的setPreviewSize方法裡做了什麼

@Override
    public void setPreviewSize() {
        String pictureRatio = mPreferences.getString(SettingConstants.KEY_PICTURE_RATIO, 
        null);
        if (pictureRatio == null) {
            List<String> supportedRatios = SettingUtils.buildPreviewRatios(mActivity,
                    mParametersExt);
            if (supportedRatios != null && supportedRatios.size() > 0) {
                SharedPreferences.Editor editor = mPreferences.edit();
                String ratioString = supportedRatios.get(0);
                editor.putString(SettingConstants.KEY_PICTURE_RATIO, ratioString);
                editor.apply();
                pictureRatio = ratioString;
            }
        }
        SettingUtils.setPreviewSize(mActivity, mParametersExt, pictureRatio);

        String pictureSize = mPreferences.getString(SettingConstants.KEY_PICTURE_SIZE, 
        null);
        int limitedResolution = SettingUtils.getLimitResolution();
        if (limitedResolution > 0) {
            int index = pictureSize.indexOf('x');
            int width = Integer.parseInt(pictureSize.substring(0, index));
            int height = Integer.parseInt(pictureSize.substring(index + 1));
            if (width * height > limitedResolution) {
                pictureSize = null;
            }
        }
        if (pictureSize == null) {
            List<String> supportedSizes = SettingUtils
                    .buildSupportedPictureSizeByRatio(mParametersExt, pictureRatio);
            SettingUtils.sortSizesInAscending(supportedSizes);
            if (limitedResolution > 0) {
                SettingUtils.filterLimitResolution(supportedSizes);
            }

            if (supportedSizes != null && supportedSizes.size() > 0) {
                pictureSize = supportedSizes.get(supportedSizes.size() - 1);
                SharedPreferences.Editor editor = mPreferences.edit();
                editor.putString(SettingConstants.KEY_PICTURE_SIZE, pictureSize);
                editor.apply();

            }
        }
        Point ps = SettingUtils.getSize(pictureSize);
        mParametersExt.setPictureSize(ps.x, ps.y);
    }

可以發現,這裡先是根據KEY_PICTURE_RATIO這個key從preference裡獲取ratio比率值,在還沒有初始化完成前,這個ratio其實是空的,於是會去構造屬性,SettingUtils.buildPreviewRatios會根據系統屬性來獲取系統支援的預覽比例,得到一個數組,從數組裡取出制定元素,存入SharedPreference裡,然後再根據這個ratio也就是比如  4:3 = 1.333  16:9=17.777

從這些double值來匹配得到指定比例屬性,然後再把這個picturesize設定給parameters去設定顯示大小。

經過除錯,發現supportedsizs這個列表的構造過程是這樣的

public static List<String> buildPreviewRatios(Context context, Parameters parameters) {
        List<String> supportedRatios = new ArrayList<String>();
        String findString = null;
        if (context != null && parameters != null) {
            // Add standard preview ratio.
            supportedRatios.add(getRatioString(4d / 3));

            mCurrentFullScreenRatio = findFullscreenRatio(context);
            List<String> fullScreenPictureSizes = buildSupportedPictureSizeByRatio(parameters,
                    getRatioString(mCurrentFullScreenRatio));
            // Add full screen ratio if platform has full screen ratio picture sizes.
            if (fullScreenPictureSizes.size() > 0) {
                findString = getRatioString(mCurrentFullScreenRatio);
                if (!supportedRatios.contains(findString)) {
                    supportedRatios.add(findString);
                }
            }
        }
        return supportedRatios;
    }

第一個元素預設是標準比例  4:3, 接著就是系統的全屏比例,但是不論如何,列表的第一個是4:3=1.333  而其他的可能是16:9或者5:2等等,會根據搜尋結果找到並新增到列表裡。

然後客戶的需要就是要設定成預設4:3,於是前面put放入preference的值,只能是get(0)第一個元素,經過驗證,開啟相機就是默認了4:3的預覽模式,不是全屏。

搞定了照片預覽模式,接著找video的預覽比例設定。

第二步:initializeSettingController

    private void initializeSettingController() {
        if (!mISettingCtrl.isSettingsInitialized()) {
            mISettingCtrl.initializeSettings(R.xml.camera_preferences, mPreferences.getGlobal(),
                    mPreferences.getLocal());
        }
        mISettingCtrl.updateSetting(mPreferences.getLocal());
        // Workaround for hdr value is on in pip video mode when pause and resume camera
        // again in pip video mode.
        if (mCameraActor.getMode() == ModePicker.MODE_VIDEO_PIP
                && mISettingCtrl.getSetting(SettingConstants.KEY_HDR) != null) {
            mISettingCtrl.onSettingChanged(SettingConstants.KEY_HDR, "off");
        }
        mMainHandler.sendEmptyMessage(MSG_CAMERA_PREFERENCE_READY);
    }

mISettingCtrl的initializeSettings

    @Override
    public void initializeSettings(int preferenceRes, SharedPreferences globalPref,
            SharedPreferences localPref) {
//        Log.d(TAG, "[initializeSettings]...");
        mGlobalPref = globalPref;
        mLocalPrefs.put(mICameraDeviceManager.getCurrentCameraId(), localPref);
        mPrefTransfer = new SharedPreferencesTransfer(globalPref, localPref);
        mSettingGenerator = new SettingGenerator(mICameraContext, mPrefTransfer);
        mSettingGenerator.createSettings(preferenceRes);
        createRules();
        mIsInitializedSettings = true;
    }

這裡有settingGenerator,並且還看到createSettings

    public void createSettings(int preferenceRes) {
        mPreferenceRes = preferenceRes;
        mInflater = new PreferenceInflater(mContext, mPrefTransfer);
        int currentCameraId = mICameraDeviceManager.getCurrentCameraId();
        PreferenceGroup group = (PreferenceGroup) mInflater.inflate(preferenceRes);
        mPreferencesGroupMap.put(currentCameraId, group);
        createSettingItems();
        createPreferences(group, currentCameraId);
    }

createSettingItems

createPreferences

兩步,建立設定的item並且建立屬性存入preference

private void createPreferences(PreferenceGroup group, int cameraId) {
//        Log.d(TAG, "[createPreferences], cameraId:" + cameraId + ", group:" + group);
        ArrayList<ListPreference> preferences = mPreferencesMap.get(cameraId);
        mSupportedImageProperties = new ArrayList<String>();
        mSupportedFaceBeautyProperties = new ArrayList<String>();
        if (preferences == null) {
            preferences = new ArrayList<ListPreference>();
            ArrayList<SettingItem> settingItems = mSettingItemsMap.get(cameraId);
            for (int settingId = 0; settingId < SettingConstants.SETTING_COUNT; settingId++) {
                String key = SettingConstants.getSettingKey(settingId);
                ListPreference preference = group.findPreference(key);

                preferences.add(preference);

                SettingItem settingItem = settingItems.get(settingId);
                settingItem.setListPreference(preference);
            }
            mPreferencesMap.put(cameraId, preferences);
        }
        // every camera maintain one setting item list.
        filterPreferences(preferences, cameraId);
    }

preference的group是在settingConstants類裡定義好的,這裡集合初步建完了

最後過濾一下, 因為列表裡是有很多preference的item,但是他們是xml裡和settingConstants預先就定義好了的陣列和結構體,他們中的一些屬性是預設就有,有些是沒有的,有些是需要從底層獲取硬體資訊從parameter裡得到,有的是需要從preference裡得到,所以構建完了,就得過濾一下,得到真實的有效的預設值。filterPreferences

    private void filterPreferences(ArrayList<ListPreference> preferences, int cameraId) {
        ArrayList<SettingItem> settingItems = mSettingItemsMap.get(cameraId);
        limitPreferencesByIntent();
        for (int i = 0; i < preferences.size(); i++) {
            // filter list preference.
            ListPreference preference = preferences.get(i);
            boolean isRemove = filterPreference(preference);
            if (isRemove) {
                preference = null;
                preferences.set(i, null);
            }
            // update setting's value and default value.
            SettingItem settingItem = settingItems.get(i);
            updateSettingItem(settingItem, preference);
        }

        overrideSettingByIntent();
    }

過濾過程,就是把每一個ListPreference給檢查一遍。然後更新資料。

從原始碼裡看,filterPreference是一個很長的switch條件選擇方法,裡面匹配了各個item的區別對待,

只看我想看到的

        case SettingConstants.ROW_SETTING_VIDEO_QUALITY:// video
            removePreference = filterUnsupportedOptions(preference,
                    getMTKSupportedVideoQuality(),
                    settingId);
            break;

VIDEO_QUALITY 視訊質量,就是那個拍攝的質量選項 

這裡看到 filterUnsupportedOptions 和getMTKSupportedVideoQuality

 private boolean filterUnsupportedOptions(ListPreference pref, List<String> supported,
            boolean resetFirst, int row) {
        if (supported != null) {
            pref.filterUnsupported(supported);
        }

        if (pref.getEntryValues().length == 1) {
            SettingItem settingItem = getSettingItem(row);
            CharSequence[] values = pref.getEntryValues();
            settingItem.setDefaultValue(values[0].toString());
            settingItem.setValue(values[0].toString());
        }

        // Remove the preference if the parameter is not supported or there is
        // only one options for the settings.
        if (supported == null || supported.size() <= 1) {
            return true;
        }

        if (pref.getEntries().length <= 1) {
            return true;
        }
        resetIfInvalid(pref, resetFirst);
        return false;
    }

就是從系統獲取可用的值,來檢測當前給出的值是否合理,如果不合理就預設用數組裡的第一個。

    private void resetIfInvalid(ListPreference pref, boolean first) {
        // Set the value to the first entry if it is invalid.
        String value = pref.getValue();
        if (pref.findIndexOfValue(value) == NOT_FOUND) {
            if (first) {
                pref.setValueIndex(0);
            } else if (pref.getEntryValues() != null && pref.getEntryValues().length > 0) {
                pref.setValueIndex(pref.getEntryValues().length - 1);
            }
        }
    }

很明顯,如果系統初始化的時候,沒有指定預設可用item就是用第一個可用的item。

在updatesettingitem

private void updateSettingItem(SettingItem settingItem, ListPreference preference) {
        int settingId = settingItem.getSettingId();
        int type = SettingConstants.getSettingType(settingId);
        String defaultValue = settingItem.getDefaultValue();
        switch (type) {
        case SettingConstants.NEITHER_IN_PARAMETER_NOR_IN_PREFERENCE:
        case SettingConstants.ONLY_IN_PARAMETER:
            // set setting default value and value, the value is initialized to
            // default value.
            defaultValue = SettingDataBase.getDefaultValue(settingId);
            if (!mIModuleCtrl.isNonePickIntent()) {
                if (SettingConstants.ROW_SETTING_CAMERA_MODE == settingId) {
                    defaultValue = Integer.toString(Parameters.CAMERA_MODE_NORMAL);
                }
            }
            settingItem.setDefaultValue(defaultValue);
            settingItem.setValue(defaultValue);
            break;

        case SettingConstants.BOTH_IN_PARAMETER_AND_PREFERENCE:
        case SettingConstants.ONLY_IN_PEFERENCE:
            Parameters parameters = mICameraDevice.getParameters();
            // if setting has preferences, its default value and value get from
            // preference.
            if (preference != null) {
                preference.reloadValue();
                if (defaultValue == null) {
                    defaultValue = generateDefaultValue(settingItem.getKey(),
                            mICameraDevice.getParameters(), preference);
                }
                settingItem.setDefaultValue(defaultValue);
                settingItem.setValue(preference.getValue());
            } else {
                if(settingItem.getKey().equals(SettingConstants.KEY_PICTURE_RATIO)){
                    settingItem.setEnable(true);
                }else{
                    settingItem.setEnable(false);
                }
            }
            break;
        default:
            break;
        }
    }

settingItem設定預設的值是從preference的預設值來的,而preference的預設值又是可用item的第一個值,於是,如果我們想要給指定的屬性值設定初始預設值就應該在把預設的preference的value改成我們自己的值。

這裡有個ONLY_IN_PREFERNCE來自於SettingConstant的定義

static {
        // setting only in preference
        SETTING_TYPE[ROW_SETTING_DUAL_CAMERA]               = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_IMAGE_PROPERTIES]          = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_RECORD_LOCATION]           = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_MICROPHONE]                = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_AUDIO_MODE]                = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_VIDEO_QUALITY]             = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_PICTURE_RATIO]             = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_VOICE]                     = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_STEREO_MODE]               = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_FACEBEAUTY_PROPERTIES]     = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_CAMERA_FACE_DETECT]        = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_ASD]                       = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_DUAL_CAMERA_MODE]          = ONLY_IN_PEFERENCE;
        SETTING_TYPE[ROW_SETTING_DNG]                       = ONLY_IN_PEFERENCE;
        。。。。

ROW_SETTING_VIDEO_QUALITY就是錄影視訊質量屬性

於是應該在settingItem.setValue(preference.getValue());這一行之前做個攔截,就能解決video的預設視訊質量值了。

Camera在啟動的時候會有很多初始化,不像其他app直接從xml或者數組裡直接取值,因為有些屬性會根據實際的硬體camera來載入,但是大部分的屬性值都在初始化的時候給設定了預設第一個或者最後一個,極少有自定義的。

這裡在除錯解決問題的時候,程式碼量太多,很容易走錯,加上慣性思維,導致在其他地方設定item的屬性時沒有生效,解決問題還是得從源頭找起,這是需要反思的。