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-mail:wxiaozhe@163.com
QQ:1226062415
Date:2011/5/14
Blog:http://blog.csdn.net/wxzking