Android平台上从Camera的jpegquality菜单研究JNI接口

来源:互联网 发布:淘宝网棉麻女装秋款 编辑:程序博客网 时间:2024/05/03 17:24

Jpeg quality菜单与picture size菜单等有些区别,在该菜单中,调用了JNI接口。第一次接触JNI接口,所以写个小文档,做个记录。

 

一、        应用程序层介绍

1、  jpeg quality菜单项及其初始化

首先,介绍一下jpeg quality菜单及其菜单项。在文件

/packages/apps/Camera/res/xml/camera_preferences.xml中,有如下的code:

<PreferenceGroup xmlns:camera=

"http://schemas.android.com/apk/res/com.android.camera">

<PreferenceGroup camera:title="@string/pref_camera_settings_category">

    ……

 <ListPreference camera:key="pref_camera_jpegquality_key"

    amera:defaultValue="@string/pref_camera_jpegquality_default" camera:title="@string/pref_camera_jpegquality_title" camera:entries="@array/pref_camera_jpegquality_entries" camera:entryValues="@array/pref_camera_jpegquality_entryvalues" />

   

  </PreferenceGroup>

  </PreferenceGroup>

Jpeg quality菜单与picture size同样都属于preferenceGroup,但是它们的初始化方式却是不一样的。在文件/packages/apps/Camera/src/com/android/camera/CameraSetting.java中,有一个函数private void initPreference(PreferenceGroup group)用来初始化picture size等菜单,但是其并没有对jpeg quality菜单做初始化。在另一个函数upgradePreferences()中,对jpeg quality菜单做了初始化。其具体定义为:

public static void upgradePreferences(SharedPreferences pref) {

        int version;

        try {

            version = pref.getInt(KEY_VERSION, 0);

        } catch (Exception ex) {

            version = 0;

        }

        if (version == CURRENT_VERSION) return;

 

        SharedPreferences.Editor editor = pref.edit();

        if (version == 0) {

            // We won't use the preference which change in version 1.

            // So, just upgrade to version 1 directly

            version = 1;

        }

        if (version == 1) {

            // Change jpeg quality {65,75,85} to {normal,fine,superfine}

            String quality = pref.getString(KEY_JPEG_QUALITY, "85");

            if (quality.equals("65")) {

                quality = "normal";

            } else if (quality.equals("75")) {

                quality = "fine";

            } else {

                quality = "superfine";

            }

            editor.putString(KEY_JPEG_QUALITY, quality);

            version = 2;

        }

        if (version == 2) {

            editor.putString(KEY_RECORD_LOCATION,

                    pref.getBoolean(KEY_RECORD_LOCATION, false)

                    ? RecordLocationPreference.VALUE_ON

                    : RecordLocationPreference.VALUE_NONE);

            version = 3;

        }

        if (version == 3) {

            // Just use video quality to replace it and

            // ignore the current settings.

            editor.remove("pref_camera_videoquality_key");

            editor.remove("pref_camera_video_duration_key");

        }

        editor.putInt(KEY_VERSION, CURRENT_VERSION);

        editor.commit();

    }

该函数在文件/packages/apps/Camera/src/com/android/camera/Camera.java中被调用,其具体code为:

public void onCreate(Bundle icicle) {

        super.onCreate(icicle);

        setContentView(R.layout.camera);

        mSurfaceView = (SurfaceView) findViewById(R.id.camera_preview);

        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);

        CameraSettings.upgradePreferences(mPreferences);   

        mQuickCapture = getQuickCaptureSettings();

        // comment out -- unused now.

        //mQuickCapture = getQuickCaptureSettings();

      ……

在文件 /packages/apps/Camera/res/values/arrays.xml中有如下的code:

- <!--

 Camera Preferences JPEG quality dialog box entries 

  -->

  <string name="pref_camera_jpegquality_entry_superfine">Super fine</string>

  <string name="pref_camera_jpegquality_entry_fine">Fine</string>

  <string name="pref_camera_jpegquality_entry_normal">Normal</string>

由此,我们可以知道jpeg quality菜单有三项,分别为Normal,Fine和SuperFine。

下面我们看看jpeg quality菜单的更新。

 

2、  jpeg quality菜单的更新

在文件/packages/apps/Camera/src/com/android/camera/Camera.java有个函数

updateCameraParametersPreference(),它负责更新camera应用程序的菜单。其定义如下:

  private void updateCameraParametersPreference() {

        // Set picture size.

        ……

        // Set the preview frame aspect ratio according to the picture size.

        ……

        // Set a preview size that is closest to the viewfinder height and has

        // the right aspect ratio.

        ……

        // Since change scene mode may change supported values,

        // Set scene mode first,

        ……

        // Set JPEG quality.

        String jpegQuality = mPreferences.getString(

                CameraSettings.KEY_JPEG_QUALITY,

                getString(R.string.pref_camera_jpegquality_default));

        mParameters.setJpegQuality(JpegEncodingQualityMappings.getQualityNumber(jpegQuality));

 

        // Set color effect parameter.

        ……

    }

 

通过String jpegQuality = mPreferences.getString(),我们获取当前jpeg quality菜单所选中的项对应的字符串,然后通过mParameters.setJpegQuality()去设置该值,底层可以得知菜单中所选中的项对应的值。mParameters.setJpegQuality()的定义在文件

/framework/base/core/java/android/hardware/camera.java中,其具体code:

     /**

         * Sets Jpeg quality of captured picture.

         *

         * @param quality the JPEG quality of captured picture. The range is 1

         *                to 100, with 100 being the best.

         */

        public void setJpegQuality(int quality) {

            set(KEY_JPEG_QUALITY, quality);

        }

其中,KEY_JPEG_QUALITY定义为:

private static final String KEY_JPEG_QUALITY = "jpeg-quality";

由注释,我们可以知道这里设置的值在[1,100]之间。通过mPreferences.getString(),我们获取的是字符串,比如normal, fine和superfine,而mParameters.setJpegQuality()所设置的字符串却应该是[0,100]之间的值,这个转换就需要

JpegEncodingQualityMappings.getQualityNumber(jpegQuality)来实现了。

       JpegEncodingQualityMappings是一个类,定义在文件

/packages/apps/Camera/src/com/android/camera/Camera.java中,具体定义为:

/*

 * Provide a mapping for Jpeg encoding quality levels

 * from String representation to numeric representation.

 */

class JpegEncodingQualityMappings {

    private static final String TAG = "JpegEncodingQualityMappings";

    private static final int DEFAULT_QUALITY = 85;

    private static HashMap<String, Integer> mHashMap =

            new HashMap<String, Integer>();

 

    static {

        mHashMap.put("normal",    CameraProfile.QUALITY_LOW);

        mHashMap.put("fine",      CameraProfile.QUALITY_MEDIUM);

        mHashMap.put("superfine", CameraProfile.QUALITY_HIGH);

    }

 

    // Retrieve and return the Jpeg encoding quality number

    // for the given quality level.

    public static int getQualityNumber(String jpegQuality) {

        Integer quality = mHashMap.get(jpegQuality);

        if (quality == null) {

            Log.w(TAG, "Unknown Jpeg quality: " + jpegQuality);

            return DEFAULT_QUALITY;

        }

        return CameraProfile.getJpegEncodingQualityParameter(quality.intValue());

    }

}

很清楚,getQualityNumber(jpegQuality)的参数如果没在normal,fine和superfine之间就返回DEFAULT_QUALITY,默认的数值;如果在,则调用

CameraProfile.getJpegEncodingQualityParameter(quality.intValue())。这个函数定义在文件

/framework/base/media/java/android/media/CameraProfile.java中。

在该文件中,定义了:

    public static final int QUALITY_LOW    = 0;

    public static final int QUALITY_MEDIUM = 1;

public static final int QUALITY_HIGH   = 2;

这表示jpeg的quality分为3个等级,它们做为getJpegEncodingQualityParameter()的参数。

    /**

     * Returns a pre-defined still image capture (jpeg) quality level

     * used for the given quality level in the Camera application.

     *

     * @param quality The target quality level

     */

    public static int getJpegEncodingQualityParameter(int quality) {

        if (quality < QUALITY_LOW || quality > QUALITY_HIGH) {

            throw new IllegalArgumentException("Unsupported quality level: " + quality);

        }

        return sJpegEncodingQualityParameters[quality];

    }

       sJpegEncodingQualityParameters的定义为:

/*

     * Cache the Jpeg encoding quality parameters

     */

    private static final int[] sJpegEncodingQualityParameters;

       数组sJpegEncodingQualityParameters中的值如何得来呢?接着往下看。

3、  Jpeg quality参数的获取

在文件CameraProfile.java中,有下面的code:

    static {

        System.loadLibrary("media_jni");

        native_init();

        sJpegEncodingQualityParameters = getImageEncodingQualityLevels();

}

private static int[] getImageEncodingQualityLevels() {

        int nLevels = native_get_num_image_encoding_quality_levels();

        if (nLevels != QUALITY_HIGH + 1) {

            throw new RuntimeException("Unexpected Jpeg encoding quality levels " + nLevels);

        }

 

        int[] levels = new int[nLevels];

        for (int i = 0; i < nLevels; ++i) {

            levels[i] = native_get_image_encoding_quality_level(i);

        }

        Arrays.sort(levels);  // Lower quality level ALWAYS comes before higher one

        return levels;

    }

 

    // Methods implemented by JNI

    private static native final void native_init();

    private static native final int native_get_num_image_encoding_quality_levels();

    private static native final int native_get_image_encoding_quality_level(int index);

}

存储jpeg quality参数的数组sJpegEncodingQualityParameters由函数

getImageEncodingQualityLevels()产生,在该函数中调用了JNI接口

native_get_num_image_encoding_quality_levels()获取了quality等级的数目,然后又通过native_get_image_encoding_quality_level(i)获取每个等级对应的值,并通过Arrays.sort(levels)对这些值进行了排序,最好返回给数组

sJpegEncodingQualityParameters。

这里动态加载了库media_jni,并且调用了三个JNI接口,下面我们对JNI层的相关code做以介绍。

 

二、        JNI层介绍

1、  JNI接口注册

在文件/framework/base/media/java/android/media/CameraProfile.java中,code

System.loadLibrary("media_jni");动态加载了库media_jni,同时会调用文件

/framework/base/media/jni/android_media_MediaPlayer.cpp中的函数JNI_OnLoad()。它负责注册android里的JNI接口。其具体code如下:

jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

    JNIEnv* env = NULL;

    jint result = -1;

 

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

        LOGE("ERROR: GetEnv failed/n");

        goto bail;

    }

    assert(env != NULL);

 

    if (register_android_media_MediaPlayer(env) < 0) {

        LOGE("ERROR: MediaPlayer native registration failed/n");

        goto bail;

    }

 

    if (register_android_media_MediaRecorder(env) < 0) {

        LOGE("ERROR: MediaRecorder native registration failed/n");

        goto bail;

    }

 

    if (register_android_media_MediaPhone(env) < 0) {

        LOGE("ERROR: MediaPhone native registration failed/n");

        goto bail;

    }

 

    if (register_android_media_MediaScanner(env) < 0) {

        LOGE("ERROR: MediaScanner native registration failed/n");

        goto bail;

    }

 

    if (register_android_media_MediaMetadataRetriever(env) < 0) {

        LOGE("ERROR: MediaMetadataRetriever native registration failed/n");

        goto bail;

    }

 

#ifndef NO_OPENCORE

    if (register_android_media_AmrInputStream(env) < 0) {

        LOGE("ERROR: AmrInputStream native registration failed/n");

        goto bail;

    }

#endif

 

    if (register_android_media_ResampleInputStream(env) < 0) {

        LOGE("ERROR: ResampleInputStream native registration failed/n");

        goto bail;

    }

 

    if (register_android_media_MediaProfiles(env) < 0) {

        LOGE("ERROR: MediaProfiles native registration failed");

        goto bail;

    }

 

    /* success -- return valid version number */

    result = JNI_VERSION_1_4;

 

bail:

    return result;

}

该函数注册了media相关的各大模块的JNI接口,其中包括MediaPlayer,MediaRecorder,MediaPhone等,当然也包括MediaProfiles。

函数register_android_media_MediaProfiles()被定义在文件

/framework/base/media/jni/android_media_MediaProfiles.cpp中。其具体code为:

int register_android_media_MediaProfiles(JNIEnv *env)

{

    int ret1 = AndroidRuntime::registerNativeMethods(env,

               kEncoderCapabilitiesClassPathName,

               gMethodsForEncoderCapabilitiesClass,

               NELEM(gMethodsForEncoderCapabilitiesClass));

 

    int ret2 = AndroidRuntime::registerNativeMethods(env,

               kCamcorderProfileClassPathName,

               gMethodsForCamcorderProfileClass,

               NELEM(gMethodsForCamcorderProfileClass));

 

    int ret3 = AndroidRuntime::registerNativeMethods(env,

               kDecoderCapabilitiesClassPathName,

               gMethodsForDecoderCapabilitiesClass,

               NELEM(gMethodsForDecoderCapabilitiesClass));

 

    int ret4 = AndroidRuntime::registerNativeMethods(env,

               kCameraProfileClassPathName,

               gMethodsForCameraProfileClass,

               NELEM(gMethodsForCameraProfileClass));

 

    // Success if all return values from above are 0

    return (ret1 || ret2 || ret3 || ret4);

}

与cameraprofiles相关的是ret4对应的函数,其中,kCameraProfileClassPathName定义为:

static const char* const kCameraProfileClassPathName =

                                            "android/media/CameraProfile";

    这代表什么意思,尚未完全清楚,暂时不写了。

数组gMethodsForCameraProfileClass包含了CameraProfile中所定义的所有JNI接口,其具体定义为:

static JNINativeMethod gMethodsForCameraProfileClass[] = {

    {"native_init",                            "()V",                    (void *)android_media_MediaProfiles_native_init},

    {"native_get_num_image_encoding_quality_levels",

                                               "()I",                    (void *)android_media_MediaProfiles_native_get_num_image_encoding_quality_levels},

    {"native_get_image_encoding_quality_level","(I)I",                   (void *)android_media_MediaProfiles_native_get_image_encoding_quality_level},

};

 

2、  JNI接口实现

上面注册的几个JNI接口的具体定义也在文件

/framework/base/media/jni/android_media_MediaProfiles.cpp中,其具体code 为:

MediaProfiles *sProfiles = NULL;

 

// This function is called from a static block in MediaProfiles.java class,

// which won't run until the first time an instance of this class is used.

static void

android_media_MediaProfiles_native_init(JNIEnv *env)

{

    LOGV("native_init");

    Mutex::Autolock lock(sLock);

 

    if (sProfiles == NULL) {

        sProfiles = MediaProfiles::getInstance();

    }

}

 

static jint

android_media_MediaProfiles_native_get_num_image_encoding_quality_levels(JNIEnv *env, jobject thiz)

{

    LOGV("native_get_num_image_encoding_quality_levels");

    return sProfiles->getImageEncodingQualityLevels().size();

}

 

static jint

android_media_MediaProfiles_native_get_image_encoding_quality_level(JNIEnv *env, jobject thiz, jint index)

{

    LOGV("native_get_image_encoding_quality_level");

    Vector<int> levels = sProfiles->getImageEncodingQualityLevels();

    if (index < 0 || index >= levels.size()) {

        jniThrowException(env, "java/lang/IllegalArgumentException", "out of array boundary");

        return -1;

    }

    return static_cast<jint>(levels[index]);

}

这里调用MediaProfiles的getInstance()和getImageEncodingQualityLevels(),这些函数如何实现,需要看看libmedia层的介绍了。

三、Libmedia层介绍

在文件/framework/base/media/libmedia/MediaProfiles.cpp中实现了MediaProfiles的getInstance()和getImageEncodingQualityLevels(),其code如下:

/*static*/ MediaProfiles*

MediaProfiles::getInstance()

{

    LOGV("getInstance");

    Mutex::Autolock lock(sLock);

    if (!sIsInitialized) {

        char value[PROPERTY_VALUE_MAX];

        if (property_get("media.settings.xml", value, NULL) <= 0) {

            const char *defaultXmlFile = "/etc/media_profiles.xml";

            FILE *fp = fopen(defaultXmlFile, "r");

            if (fp == NULL) {

                LOGW("could not find media config xml file");

                sInstance = createDefaultInstance();

            } else {

                fclose(fp);  // close the file first.

                sInstance = createInstanceFromXmlFile(defaultXmlFile);

            }

        } else {

            sInstance = createInstanceFromXmlFile(value);

        }

    }

 

    return sInstance;

}

该函数中判断,如果"/etc/media_profiles.xml"存在就调用函数

createInstanceFromXmlFile()去解析该xml文件,否则调用createDefaultInstance()创建默认的数值。

函数createDefaultInstance()的实现如下:

/*static*/ MediaProfiles*

MediaProfiles::createDefaultInstance()

{

    MediaProfiles *profiles = new MediaProfiles;

    createDefaultCamcorderProfiles(profiles);

    createDefaultVideoEncoders(profiles);

    createDefaultAudioEncoders(profiles);

    createDefaultVideoDecoders(profiles);

    createDefaultAudioDecoders(profiles);

    createDefaultEncoderOutputFileFormats(profiles);

    createDefaultImageEncodingQualityLevels(profiles);

    sIsInitialized = true;

    return profiles;

}

    函数createDefaultImageEncodingQualityLevels()实现如下:

/*static*/ void

MediaProfiles::createDefaultImageEncodingQualityLevels(MediaProfiles *profiles)

{

    profiles->mImageEncodingQualityLevels.add(70);

    profiles->mImageEncodingQualityLevels.add(80);

    profiles->mImageEncodingQualityLevels.add(90);

}

    由此,可以看出,默认情况下,会将70,80,90做为jpeg quality的三个quality等级对应的数值。

    如果xml文件存在,就需要看看函数createInstanceFromXmlFile()了,其定义为:

/*static*/ MediaProfiles*

MediaProfiles::createInstanceFromXmlFile(const char *xml)

{

    FILE *fp = NULL;

    CHECK((fp = fopen(xml, "r")));

 

    XML_Parser parser = ::XML_ParserCreate(NULL);

    CHECK(parser != NULL);

 

    MediaProfiles *profiles = new MediaProfiles();

    ::XML_SetUserData(parser, profiles);

    ::XML_SetElementHandler(parser, startElementHandler, NULL);

 

    /*

      FIXME:

      expat is not compiled with -DXML_DTD. We don't have DTD parsing support.

 

      if (!::XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS)) {

          LOGE("failed to enable DTD support in the xml file");

          return UNKNOWN_ERROR;

      }

 

    */

 

    const int BUFF_SIZE = 512;

    for (;;) {

        void *buff = ::XML_GetBuffer(parser, BUFF_SIZE);

        if (buff == NULL) {

            LOGE("failed to in call to XML_GetBuffer()");

            delete profiles;

            profiles = NULL;

            goto exit;

        }

 

        int bytes_read = ::fread(buff, 1, BUFF_SIZE, fp);

        if (bytes_read < 0) {

            LOGE("failed in call to read");

            delete profiles;

            profiles = NULL;

            goto exit;

        }

 

        CHECK(::XML_ParseBuffer(parser, bytes_read, bytes_read == 0));

 

        if (bytes_read == 0) break;  // done parsing the xml file

    }

 

exit:

    ::XML_ParserFree(parser);

    ::fclose(fp);

    if (profiles) {

        sIsInitialized = true;

    }

    return profiles;

}

    函数getImageEncodingQualityLevels()的定义为:

Vector<int> MediaProfiles::getImageEncodingQualityLevels() const

{

    return mImageEncodingQualityLevels;  // copy out

}

    只需返回存储了jpeg quality数值的数组mImageEncodingQualityLevels就行了。

 

 

四、小结

通过研究jpeg quality的菜单,简单了解了JNI接口的注册和实现流程。虽然里面还有不少不大清楚地地方,但此记录可以帮助日后使用JNI接口时使用。

 

 

E-mailwxiaozhe@163.com

QQ1226062415
Date
2011/5/14
Blog
http://blog.csdn.net/wxzking

 

 

0 0
原创粉丝点击