1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AndroidでAndroid Neural Networks APIを使ってデバイスの情報を取得する

Posted at

動機

世の中Deep Neural Networkばやりだが、クラウドのAPIを叩くのはデータが持っていかれそうで怖いので、Androidのデバイス内でできないのかと思っていたら、AndroidにAndroid Neural Networks API(NNAPI)というものがあるというので、小手調べに、デバイスの情報を取り出してみた。

環境

OS
Android 12
Hard
Sense 4
IDE
Android Studio Dolphin | 2021.3.1 Patch 1

手順

  1. AndroidStudio で Native C++ を選んでプロジェクトを作る

2. gradle の設定

build.gradle(:app)
android {
...
   externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.22.1'
        }
    }
    buildFeatures {
        viewBinding true
    }
    ndkVersion "25.1.8937393"
}

Native C++ を選ぶと自動的に生成されるが、必要ならexternalNativeBuildndkVersionのバージョンを、使用するもの(ダウンロードしたもの)に合わせておく。
表示で利用するので、viewBindingを追記した。

3. Device数の取得

Native C++でプロジェクトを生成すると、native-lib.cppというものが作成されるので、これを利用することにする。その前に、CMakeでのコンパイルのための情報である CMakeLists.txt でNNAPIを使うための宣言を追加する。

CMakeLists.txt
target_link_libraries( # Specifies the target library.
        ...
        # Link with libneuralnetworks.so for NN API
        neuralnetworks
        android
        ...)

次が本題のNNAPIである。まず、簡単なところで、Device数を取得してみる。NNAPIの関数はアドレス渡しで引数に指定した変数に返り値を入れる仕様になっている。C++の長ったらしい関数名は native-lib.cpp に例が作成されていると思うので、それを参考に設定する。

native-lib.cpp
#include <android/NeuralNetworks.h>

extern "C" JNIEXPORT jint JNICALL
Java_com_yoo6309_firstndkapplication_MainActivity_getDeviceCount(
        JNIEnv* env,
        jobject /* this */) {
    uint32_t numDevices;
    ANeuralNetworks_getDeviceCount(&numDevices);
    return (jint)numDevices;
}

Native C++ では JNI を使うことになっているので、NNAPIで取得したデバイス数であるnumDevicesを、javaのint型に対応するjintに変換してjava側に返すことになる。
これを使うjava側のプログラムは以下のようになる。関数をnativeで宣言するだけ。楽勝である。

MainActivity.java
int nDevice = getDeviceCount();
...
public native int getDeviceCount();

4. デバイス情報を取得する

デバイス情報としては、デバイス検出に挙げられた以下の関数がある。

ANeuralNetworksDevice_getFeatureLevel
ANeuralNetworksDevice_getName
ANeuralNetworksDevice_getType
ANeuralNetworksDevice_getVersion

1つずつ取得するのはだるいので、クラスを使いたいところだ。C++側からクラスのインスタンスを生成して値を返す方法については、夏休みの宿題は、JNIを参考にした。JNIの勉強が必要だ。また、NNAPIの実際の使い方は、TensorFlowソースコードを参考にした。
まず、返り値用のクラスをjava側で宣言する。

MainActivity.java
    public static class NNDevice {
        long feature;
        String name;
        int type;
        String version;

        public NNDevice(long feature, String name, int type, String version) {
            this.feature = feature;
            this.name = name;
            this.type = type;
            this.version = version;
        }
    }

C++では、NNAPIの各関数で取得した値を、このクラスから生成したインスタンスの中に設定して返すことになる。FindClassのクラスの指定と、GetMethodIDでのmethod signatureの指定がわかりにくい。以下の例では、long, String, int, Stringの引数(入力)があり、出力がvoidという指定をしている。

native-lib.cpp
extern "C" JNIEXPORT jobject JNICALL
Java_com_yoo6309_firstndkapplication_MainActivity_getDeviceParam(
        JNIEnv* env,
        jobject MainActivity /* this */,
        jint i) {
    ANeuralNetworksDevice* device;
    int64_t featureLevel;
    int32_t type;
    const char* buffer;
    int ans = ANeuralNetworks_getDevice((uint32_t)i, &device);
    ans = ANeuralNetworksDevice_getFeatureLevel(device, &featureLevel);
    ans = ANeuralNetworksDevice_getName(device, &buffer);
    jstring name = env->NewStringUTF(buffer);
    ans = ANeuralNetworksDevice_getType(device, &type);
    ans = ANeuralNetworksDevice_getVersion(device, &buffer);
    jstring version = env->NewStringUTF(buffer);
    jclass deviceClass = env->FindClass("com/yoo6309/firstndkapplication/MainActivity$NNDevice");
    jmethodID deviceConstructor;
    deviceConstructor = env->GetMethodID(deviceClass, "<init>",
                                         "(JLjava/lang/String;ILjava/lang/String;)V");
    jobject ret = env->NewObject(deviceClass,
                             deviceConstructor,
                             featureLevel,
                             name,
                             type,
                             version);
    return ret;
}

これを呼ぶjava側のプログラムは、以下のような感じ。

int i;
...
nnDevice = getDeviceParam(i);
...
public native NNDevice getDeviceParam(int i);

5. ソース全体

MainActivity.java
package com.yoo6309.firstndkapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.yoo6309.firstndkapplication.databinding.ActivityMainBinding;

import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'firstndkapplication' library on application startup.
    static {
        System.loadLibrary("firstndkapplication");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        NNDevice nnDevice;

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        int nDevice = getDeviceCount();
        tv.setText(String.format(Locale.JAPAN, "Device Count: %d", nDevice));
        StringBuilder msg = new StringBuilder();
        for(int i = 0; i < nDevice; i++) {
            nnDevice = getDeviceParam(i);
            if (i > 0) msg.append("\n");
            msg.append("feature:").append(nnDevice.feature).append("\nname:").append(nnDevice.name).append("\ntype:").append(nnDevice.type).append("\nversion:").append(nnDevice.version);
        }
        binding.deviceParamText.setText(msg.toString());
    }

    public static class NNDevice {
        long feature;
        String name;
        int type;
        String version;

        public NNDevice(long feature, String name, int type, String version) {
            this.feature = feature;
            this.name = name;
            this.type = type;
            this.version = version;
        }
    }
    /**
     * A native method that is implemented by the 'firstndkapplication' native library,
     * which is packaged with this application.
     */
    public native int getDeviceCount();
    public native NNDevice getDeviceParam(int i);
}
native-lib.cpp
#include <jni.h>
#include <string>
#include <android/NeuralNetworks.h>

extern "C" JNIEXPORT jint JNICALL
Java_com_yoo6309_firstndkapplication_MainActivity_getDeviceCount(
        JNIEnv* env,
        jobject /* this */) {
    uint32_t numDevices;
    ANeuralNetworks_getDeviceCount(&numDevices);
    return (jint)numDevices;
}

extern "C" JNIEXPORT jobject JNICALL
Java_com_yoo6309_firstndkapplication_MainActivity_getDeviceParam(
        JNIEnv* env,
        jobject MainActivity /* this */,
        jint i) {
    ANeuralNetworksDevice* device;
    int64_t featureLevel;
    int32_t type;
    const char* buffer;
    int ans = ANeuralNetworks_getDevice((uint32_t)i, &device);
    ans = ANeuralNetworksDevice_getFeatureLevel(device, &featureLevel);
    ans = ANeuralNetworksDevice_getName(device, &buffer);
    jstring name = env->NewStringUTF(buffer);
    ans = ANeuralNetworksDevice_getType(device, &type);
    ans = ANeuralNetworksDevice_getVersion(device, &buffer);
    jstring version = env->NewStringUTF(buffer);
    jclass deviceClass = env->FindClass("com/yoo6309/firstndkapplication/MainActivity$NNDevice");
    jmethodID deviceConstructor;
    deviceConstructor = env->GetMethodID(deviceClass, "<init>",
                                         "(JLjava/lang/String;ILjava/lang/String;)V");
    jobject ret = env->NewObject(deviceClass,
                             deviceConstructor,
                             featureLevel,
                             name,
                             type,
                             version);
    return ret;
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/sample_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="device count"
        android:textAlignment="gravity"
        app:layout_constraintBottom_toTopOf="@+id/device_param_text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/device_param_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="device param"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/sample_text" />

</androidx.constraintlayout.widget.ConstraintLayout>

6. 実行

これをsense 4で実行したところ、以下のような出力を得た。Qualcommによれば、gpuはAdreno 618、htaは、"Qualcomm® Hexagon™ Tensor Accelerator"というものらしい。

Screenshot_20230122-182443.png

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?