Wrote a benchmark activity for Android which reads images recursively from the SD card, decodes each 10 times, and prints the average time to decode in milliseconds. The UI is extremely minimal, as the results are written to the log, but this is a good start towards measuring our performance under Dalvik on a platform without floating point hardware. This should run fine on the emulator but I haven't tested it yet.

git-svn-id: https://zxing.googlecode.com/svn/trunk@640 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
dswitkin 2008-10-24 22:05:07 +00:00
parent f83e68d7bc
commit de419268b8
10 changed files with 447 additions and 9 deletions

View file

@ -28,6 +28,8 @@
<activity android:name="CameraTestActivity"
android:screenOrientation="landscape">
</activity>
<activity android:name="BenchmarkActivity"
android:label="@string/benchmark_name"/>
</application>
<uses-permission android:name="android.permission.CAMERA"/>
</manifest>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2008 ZXing authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_horizontal"
android:padding="10px">
<TableRow>
<Button android:id="@+id/benchmark_run"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/benchmark_run"/>
</TableRow>
<TableRow>
<TextView android:id="@+id/benchmark_help"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="false"
android:text="@string/benchmark_help"/>
</TableRow>
</TableLayout>

View file

@ -19,6 +19,16 @@
android:layout_height="fill_parent"
android:gravity="center_horizontal"
android:padding="10px">
<TableRow>
<Button android:id="@+id/test_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/test_camera"/>
<Button android:id="@+id/run_benchmark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/benchmark_run"/>
</TableRow>
<TableRow>
<Button android:id="@+id/scan_product"
android:layout_width="wrap_content"
@ -30,10 +40,10 @@
android:text="@string/scan_qr_code"/>
</TableRow>
<TableRow>
<Button android:id="@+id/test_camera"
<Button android:id="@+id/scan_anything"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/test_camera"/>
android:text="@string/scan_anything"/>
<Button android:id="@+id/search_book_contents"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View file

@ -15,8 +15,8 @@
limitations under the License.
-->
<resources>
<!-- Messages IDs -->
<item type="id" name="auto_focus"/>
<item type="id" name="benchmark_done"/>
<item type="id" name="quit"/>
<item type="id" name="save"/>
<item type="id" name="save_succeeded"/>

View file

@ -16,6 +16,7 @@
-->
<resources>
<string name="app_name">ZXing Test</string>
<string name="scan_anything">Scan anything</string>
<string name="scan_product">Scan product</string>
<string name="scan_qr_code">Scan QR Code</string>
<string name="search_book_contents">Search Book Contents</string>
@ -33,4 +34,9 @@
<string name="status_message">Press the shutter button to save images to the SD card for testing purposes. Press DPAD_CENTER to trigger autofocus.</string>
<string name="save_succeeded">Save succeeded</string>
<string name="save_failed">Save failed - is the SD card installed?</string>
<string name="benchmark_name">ZXing Benchmark</string>
<string name="benchmark_help">Place images in /sdcard/zxingbenchmark, then check \"adb logcat\" for results. Turn on Airplane Mode first for more reliable results.</string>
<string name="benchmark_run">Run benchmark</string>
<string name="benchmark_running">Benchmark running...\u2026</string>
</resources>

View file

@ -0,0 +1,96 @@
/*
* Copyright (C) 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.androidtest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import java.util.Vector;
public class BenchmarkActivity extends Activity {
private static final String PATH = "/sdcard/zxingbenchmark";
private static final String TAG = "ZXingBenchmark";
private Button mRunBenchmarkButton;
private TextView mTextView;
private BenchmarkThread mBenchmarkThread;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.benchmark);
mRunBenchmarkButton = (Button) findViewById(R.id.benchmark_run);
mRunBenchmarkButton.setOnClickListener(mRunBenchmark);
mTextView = (TextView) findViewById(R.id.benchmark_help);
mBenchmarkThread = null;
}
public Button.OnClickListener mRunBenchmark = new Button.OnClickListener() {
public void onClick(View v) {
if (mBenchmarkThread == null) {
mRunBenchmarkButton.setEnabled(false);
mTextView.setText(R.string.benchmark_running);
mBenchmarkThread = new BenchmarkThread(BenchmarkActivity.this, PATH);
mBenchmarkThread.start();
}
}
};
public Handler mHandler = new Handler() {
public void handleMessage(Message message) {
switch (message.what) {
case R.id.benchmark_done:
handleBenchmarkDone(message);
mBenchmarkThread = null;
mRunBenchmarkButton.setEnabled(true);
mTextView.setText(R.string.benchmark_help);
break;
default:
break;
}
}
};
private void handleBenchmarkDone(Message message) {
Vector<BenchmarkItem> items = (Vector<BenchmarkItem>) message.obj;
int count = 0;
for (int x = 0; x < items.size(); x++) {
BenchmarkItem item = items.get(x);
if (item != null) {
BarcodeFormat format = item.getFormat();
Log.v(TAG, (item.getDecoded() ? "DECODED: " : "FAILED: ") + item.getPath());
Log.v(TAG, " Ran " + item.getCount() + " tests on " +
(format != null ? format.toString() : "unknown") + " format with an average of " +
item.getAverageMilliseconds() + " ms");
count++;
}
}
Log.v(TAG, "TOTAL: Decoded " + count + " images");
}
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.androidtest;
import com.google.zxing.BarcodeFormat;
public class BenchmarkItem {
private String mPath;
private int[] mTimes;
private int mPosition;
private boolean mDecoded;
private BarcodeFormat mFormat;
public BenchmarkItem(String path, int runs) {
mPath = path;
mTimes = new int[runs];
mPosition = 0;
mDecoded = false;
mFormat = null;
}
// I'm storing these separately instead of as a running total so I can add features like
// calculating the min and max later, or ignoring outliers.
public void addResult(int milliseconds) {
mTimes[mPosition] = milliseconds;
mPosition++;
}
public void setDecoded(boolean decoded) {
mDecoded = decoded;
}
public void setFormat(BarcodeFormat format) {
mFormat = format;
}
public String getPath() {
return mPath;
}
public int getAverageMilliseconds() {
int size = mTimes.length;
int total = 0;
for (int x = 0; x < size; x++) {
total += mTimes[x];
}
if (size > 0) {
return total / size;
} else {
return 0;
}
}
public int getCount() {
return mTimes.length;
}
public boolean getDecoded() {
return mDecoded;
}
public BarcodeFormat getFormat() {
return mFormat;
}
}

View file

@ -0,0 +1,102 @@
/*
* Copyright (C) 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.androidtest;
import android.os.Message;
import android.util.Log;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Date;
import java.util.Vector;
final class BenchmarkThread extends Thread {
private static final String TAG = "BenchmarkThread";
private static final int RUNS = 10;
private BenchmarkActivity mActivity;
private String mPath;
private MultiFormatReader mMultiFormatReader;
BenchmarkThread(BenchmarkActivity activity, String path) {
mActivity = activity;
mPath = path;
}
@Override
public void run() {
mMultiFormatReader = new MultiFormatReader();
mMultiFormatReader.setHints(null);
Vector<BenchmarkItem> items = new Vector<BenchmarkItem>();
walkTree(mPath, items);
Message message = Message.obtain(mActivity.mHandler, R.id.benchmark_done);
message.obj = items;
message.sendToTarget();
}
// Recurse to allow subdirectories
private void walkTree(String path, Vector<BenchmarkItem> items) {
File file = new File(path);
if (file.isDirectory()) {
String[] files = file.list();
for (int x = 0; x < files.length; x++) {
walkTree(file.getAbsolutePath() + "/" + files[x], items);
}
} else {
BenchmarkItem item = decode(path);
if (item != null) {
items.addElement(item);
}
}
}
private BenchmarkItem decode(String path) {
RGBMonochromeBitmapSource source = null;
try {
source = new RGBMonochromeBitmapSource(path);
} catch (FileNotFoundException e) {
Log.e(TAG, e.toString());
return null;
}
BenchmarkItem item = new BenchmarkItem(path, RUNS);
for (int x = 0; x < RUNS; x++) {
Date startDate = new Date();
boolean success;
Result result = null;
try {
result = mMultiFormatReader.decodeWithState(source);
success = true;
} catch (ReaderException e) {
success = false;
}
Date endDate = new Date();
if (x == 0) {
item.setDecoded(success);
item.setFormat(result != null ? result.getBarcodeFormat() : null);
}
item.addResult((int) (endDate.getTime() - startDate.getTime()));
}
return item;
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.androidtest;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.google.zxing.common.BaseMonochromeBitmapSource;
import java.io.FileNotFoundException;
public class RGBMonochromeBitmapSource extends BaseMonochromeBitmapSource {
private int mWidth;
private int mHeight;
private byte[] mLuminances;
public RGBMonochromeBitmapSource(String path) throws FileNotFoundException {
Bitmap bitmap = BitmapFactory.decodeFile(path);
if (bitmap == null) {
throw new FileNotFoundException("Couldn't open " + path);
}
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
// In order to measure pure decoding speed, we convert the entire image to a greyscale array up
// front, which is the same as the Y channel of the YUVMonochromeBitmapSource in the real app.
mLuminances = new byte[width * height];
mWidth = width;
mHeight = height;
for (int y = 0; y < height; y++) {
int offset = y * height;
for (int x = 0; x < width; x++) {
int pixel = pixels[offset + x];
int r = (pixel >> 16) & 0xff;
int g = (pixel >> 8) & 0xff;
int b = pixel & 0xff;
if (r == g && g == b) {
// Image is already greyscale, so pick any channel
mLuminances[offset + x] = (byte) r;
} else {
// Calculate luminance cheaply, favoring green
mLuminances[offset + x] = (byte) ((r + g + g + b) >> 2);
}
}
}
}
public int getHeight() {
return mHeight;
}
public int getWidth() {
return mWidth;
}
public int getLuminance(int x, int y) {
return mLuminances[y * mWidth + x] & 0xff;
}
public void cacheRowForLuminance(int y) {
}
public void cacheColumnForLuminance(int x) {
}
}

View file

@ -32,14 +32,20 @@ public class ZXingTestActivity extends Activity {
setContentView(R.layout.test);
Button test_camera = (Button) findViewById(R.id.test_camera);
test_camera.setOnClickListener(mTestCamera);
Button run_benchmark = (Button) findViewById(R.id.run_benchmark);
run_benchmark.setOnClickListener(mRunBenchmark);
Button scan_product = (Button) findViewById(R.id.scan_product);
scan_product.setOnClickListener(mScanProduct);
Button scan_qr_code = (Button) findViewById(R.id.scan_qr_code);
scan_qr_code.setOnClickListener(mScanQRCode);
Button test_camera = (Button) findViewById(R.id.test_camera);
test_camera.setOnClickListener(mTestCamera);
Button scan_anything = (Button) findViewById(R.id.scan_anything);
scan_anything.setOnClickListener(mScanAnything);
Button search_book_contents = (Button) findViewById(R.id.search_book_contents);
search_book_contents.setOnClickListener(mSearchBookContents);
@ -66,6 +72,22 @@ public class ZXingTestActivity extends Activity {
encode_bad_data.setOnClickListener(mEncodeBadData);
}
public Button.OnClickListener mTestCamera = new Button.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClassName(ZXingTestActivity.this, CameraTestActivity.class.getName());
startActivity(intent);
}
};
public Button.OnClickListener mRunBenchmark = new Button.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClassName(ZXingTestActivity.this, BenchmarkActivity.class.getName());
startActivity(intent);
}
};
public Button.OnClickListener mScanProduct = new Button.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent("com.google.zxing.client.android.SCAN");
@ -82,11 +104,10 @@ public class ZXingTestActivity extends Activity {
}
};
public Button.OnClickListener mTestCamera = new Button.OnClickListener() {
public Button.OnClickListener mScanAnything = new Button.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClassName(ZXingTestActivity.this, CameraTestActivity.class.getName());
startActivity(intent);
Intent intent = new Intent("com.google.zxing.client.android.SCAN");
startActivityForResult(intent, 0);
}
};