1

I have made android app with jni C++ code.

To read model file while creating apk on android native code(C++), I placed the model file in assets folder. (so file path is my_app/src/main/assets/model.tflite)

Then, I tried to read the model with following code but an error has raised:

Kotlin code:

private val context: Context
    get() = getApplication<Application>().applicationContext

asset_manager = context.assets

C++ code:

AAssetManager* mgr = AAssetManager_fromJava(env, asset_manager);
AAssetDir* assetDir = AAssetManager_openDir(mgr, "");
const char* filename = (const char*)NULL;
while ((filename = AAssetDir_getNextFileName(assetDir)) != NULL) {
    AAsset* asset = AAssetManager_open(mgr, filename, AASSET_MODE_STREAMING);
    __android_log_print(ANDROID_LOG_INFO, "tag", "file name is: %s", filename);  <- file name reading is ok
    char buf[BUFSIZ];
    int nb_read = 0;
    FILE* out = fopen(filename, "w");                            <- out has null FILE*
    while ((nb_read = AAsset_read(asset, buf, BUFSIZ)) > 0)
        fwrite(buf, nb_read, 1, out);
    fclose(out);
    AAsset_close(asset);
}
AAssetDir_close(assetDir);

logcat :

2022-06-19 22:45:54.449 12180-12180/com.my.first.app I/tag: file name is: model.tflite
2022-06-19 22:45:54.449 12180-12180/com.my.first.app A/libc: FORTIFY: fwrite: null FILE*
2022-06-19 22:45:54.450 12180-12180/com.my.first.app A/libc: Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 12180 (st.app), pid 12180 (st.app)

Why does fopen(filename, "w") return null? It seems that AAssetManager is work.

YeongHwa Jin
  • 435
  • 5
  • 15
  • Related: https://stackoverflow.com/a/11064252/295004 – Morrison Chang Jun 20 '22 at 05:27
  • Failure to open some file for writing with `fopen` has nothing to do with reading an asset. And it is certainly not possible to open an asset for writing. `AAsset` object makes it possible to read asset content but it does not imply existence of `filename` file. – user7860670 Jun 20 '22 at 07:06
  • Do you really need the file to be on disk? If so, you need to write to a directory you actually have write access to, such as your APKs cache or file directory. – Botje Jun 20 '22 at 07:36
  • Do you really need to do this via JNI? – user207421 Jun 20 '22 at 07:50
  • @MorrisonChang Thx for reply. But my code is refer to https://stackoverflow.com/questions/65273837/android-native-file-read-from-assets-folder-by-tflite-buildfromfile. How does work it? – YeongHwa Jin Jun 20 '22 at 08:03
  • @Botje Does it mean that I also need to let my `model.tflite` file is placed in physical phone? – YeongHwa Jin Jun 20 '22 at 08:05
  • @user207421 Yes... Unfortunately inference code is written by C++ :( – YeongHwa Jin Jun 20 '22 at 08:06
  • Your code clearly copies the asset file's contents to a temporary file, so I assume you wanted this for some reason. Can't the TFLite framework read contents from a memory buffer? – Botje Jun 20 '22 at 08:18
  • @Botje I'm not ensure, but that seems possible. https://www.tensorflow.org/lite/api_docs/cc/class/tflite/flat-buffer-model#buildfromallocation. however, it is still confusing for me... :( – YeongHwa Jin Jun 20 '22 at 08:28

2 Answers2

1

Looks like you want to combine this loading code to load the asset into a memory buffer and then use the BuildFromAllocation method:

std::unique_ptr<tflite::Allocation> readAsset(std::string target) {
  AAssetManager* mgr = AAssetManager_fromJava(env, asset_manager);
  AAssetDir* assetDir = AAssetManager_openDir(mgr, "");
  const char* filename = nullptr;
  while ((filename = AAssetDir_getNextFileName(assetDir)) != nullptr) {
    if (target != filename)
      continue;

    AAsset* asset = AAssetManager_open(mgr, filename, AASSET_MODE_STREAMING);
    auto len = AAsset_getLength(asset);
    uint8_t *buf = new uint8_t[len];
    std::copy_n(AAsset_getBuffer(asset), len, buf);
    auto ret = std::make_unique<tflite::Allocation>(buf , len);
    AAsset_close(asset);
    AAssetDir_close(assetDir);
    return ret;
  }
  AAssetDir_close(assetDir);
  return {};
}

and then use it later as:

auto ptr = readAssset("model.tflite");
if (!ptr) {
  // do something
}

auto modelPtr = tflite::FlatBufferModel::BuildFromAllocation(ptr, some_error_reporter);
if (!modelPtr) {
  // do something
}

Note: this will leak memory, as TFlite takes ownership of the buffer.

Botje
  • 26,269
  • 3
  • 31
  • 41
1

Thanks to @Botje, I can load the model.tflite in assets by this way:

AAssetManager* mgr = AAssetManager_fromJava(env, asset_manager);
AAssetDir* assetDir = AAssetManager_openDir(mgr, "");
const char* filename = nullptr;
while ((filename = AAssetDir_getNextFileName(assetDir)) != nullptr) {
    AAsset* asset = AAssetManager_open(mgr, filename, AASSET_MODE_STREAMING);
    size_t size = AAsset_getLength(asset);
    buf = new char[size];
    memmove(buf, AAsset_getBuffer(asset), size);

    AAsset_close(asset);
  }
AAssetDir_close(assetDir);

std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromBuffer((const char*)buf, size);
std::unique_ptr<tflite::Interpreter> interpreter;
tflite::ops::builtin::BuiltinOpResolver op_resolver;
tflite::InterpreterBuilder(*model, op_resolver)(&interpreter);
YeongHwa Jin
  • 435
  • 5
  • 15