【问题标题】:Using AssetManager class from Android NDK in a Flutter app在 Flutter 应用中使用来自 Android NDK 的 AssetManager 类
【发布时间】:2020-07-24 06:25:25
【问题描述】:

我一直在尝试在我的 Flutter 应用中使用 Android NDK 的 AssetManager 类来访问音频文件,该类与 Google Oboe 配合使用。按照双簧管存储库中的this 示例,我了解到他们从Java 中获取AssetManager,如下所示:

JNIEXPORT void JNICALL
Java_com_google_oboe_sample_rhythmgame_MainActivity_native_1onStart(JNIEnv *env, jobject instance,
                                                                     jobject jAssetManager) {

    AAssetManager *assetManager = AAssetManager_fromJava(env, jAssetManager);
    if (assetManager == nullptr) {
        LOGE("Could not obtain the AAssetManager");
        return;
    }

    game = std::make_unique<Game>(*assetManager);
    game->start();
}

基本上使用参数jAssetManager,它们通过JNI 接口从Java 传递到C++ 函数。现在我不使用 JNI,因为我使用的是 Flutter 和 Dart,而 Dart 中与 C++ 函数通信的方式是通过 dart:ffi,但因为我可以创建 AssetManager 的唯一方法是使用 AAssetManager_fromJava(env, jAssetManager) ,我需要这两个参数,我找不到用 Flutter 和 Dart 替换的方法。

我做了一些研究,当我创建 Flutter FFI 插件时,显然 Dart 代码与 Kotlin 代码通信,然后调用本机 C++ 函数。

这是我的 C++ 函数:

EXTERNC void *engine_create(void) {
    AAssetManager *assetManager = AAssetManager_fromJava(env, jAssetManager);   // ERROR: How do I get these?
    if (assetManager == nullptr) {
        LOGE("Could not obtain the AAssetManager");
        return nullptr;
    }   

    return new DSPAudioEngine(*assetManager);
}

这是该函数的 Dart 包装器:

import 'dart:ffi';
import 'dart:typed_data';

import 'package:ffi/ffi.dart';
import 'package:flutter/services.dart';

typedef oboe_engine_init = Pointer<Void> Function();
typedef OboeEngineInit = Pointer<Void> Function();

class FfiGoogleOboe {
  static const MethodChannel _channel =
      const MethodChannel('ffi_google_oboe');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  static FfiGoogleOboe _instance;

  factory FfiGoogleOboe() {
    if (_instance == null) {
      _instance = FfiGoogleOboe._();
    }
    return _instance;
  }


  OboeEngineInit _engineInit;

  FfiGoogleOboe._() {
    final oboeLib = DynamicLibrary.open('libffi_google_oboe.so');

    _engineInit = oboeLib
        .lookup<NativeFunction<oboe_engine_init>>('engine_create')
        .asFunction();
  }

}

这是我在 FFI 插件实现中找到的 Kotlin 代码:

package g1_assd_2020.ffi_google_oboe

import androidx.annotation.NonNull;

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import android.content.res.AssetManager

/** FfiGoogleOboePlugin */
public class FfiGoogleOboePlugin: FlutterPlugin, MethodCallHandler {
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private lateinit var channel : MethodChannel

  override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "ffi_google_oboe")
    channel.setMethodCallHandler(this);
  }

  // This static function is optional and equivalent to onAttachedToEngine. It supports the old
  // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
  // plugin registration via this function while apps migrate to use the new Android APIs
  // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
  //
  // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
  // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
  // depending on the user's project. onAttachedToEngine or registerWith must both be defined
  // in the same class.
  companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      val channel = MethodChannel(registrar.messenger(), "ffi_google_oboe")
      channel.setMethodCallHandler(FfiGoogleOboePlugin())
    }
  }

  override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }

  override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
    channel.setMethodCallHandler(null)
  }
}

最后,这是 Oboe 的人们如何使用 JNI 和 Java 处理它的:

package com.google.oboe.sample.rhythmgame;

import android.content.Context;
import android.content.res.AssetManager;
import androidx.appcompat.app.AppCompatActivity;

import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager;

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setDefaultStreamValues(this);
    }

    protected void onResume(){
        super.onResume();
        native_onStart(getAssets());
    }

    protected void onPause(){
        super.onPause();
        native_onStop();
    }

    static void setDefaultStreamValues(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
            AudioManager myAudioMgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
            String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
            int defaultSampleRate = Integer.parseInt(sampleRateStr);
            String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
            int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr);

            native_setDefaultStreamValues(defaultSampleRate, defaultFramesPerBurst);
        }
    }

    private native void native_onStart(AssetManager assetManager);
    private native void native_onStop();
    private static native void native_setDefaultStreamValues(int defaultSampleRate,
                                                      int defaultFramesPerBurst);
}

【问题讨论】:

  • 我认为您使用 RootBundle 加载资产然后使用 ffi 将字节传递给 C 的初始方法更容易开始工作。
  • 我得出了同样的结论。我想消除错误读取文件的可能性(特别是如果文件像 mp3 一样被压缩),并且由于我在 Dart 中找不到库来从 .wav.mp3 获取 PCM 数据,所以我想效仿双簧管的例子。但是我没有找到使用 Flutter 的方法,所以我现在要做的是使用 rootBundle 将字节缓冲区向下传递到 C++,然后从那里解码。
  • 基本上,我正在尝试重新实现方法AAssetDataSource* newFromCompressedAsset(AAssetManager &amp;assetManager, size_t len, AudioProperties *outputProperties); 以采用float* buffer 而不是AAssetManager &amp;assetManager。我指的方法在this文件第31行。
  • 在 Dart 中从 WAV 中获取 PCM 很容易,而 mp3 则不那么容易,因此您可能希望将 AMediaCodec 与由资产字节支持的自定义数据源一起使用。请参阅:developer.android.com/ndk/reference/group/…github.com/google/oboe/blob/master/samples/RhythmGame/src/main/…
  • 您需要一个 uint_8 缓冲区作为输入,而不是浮点数,因为这是您将从资产中读取并使用 ffi 传递的内容。

标签: c++ flutter android-ndk oboe dart-ffi


【解决方案1】:

基本上,您需要将 AssetManager 引用从插件的 Kotlin 文件传递​​到 C++ 库。这个答案解释了如何让 Kotlin 文件调用 C++ 代码:Android: How to call ndk function from Kotlin?

您需要使用 methodChannel 调用来触发它。您可以从flutterPluginBinding.applicationContext.assets 的 onAttachedToEngine 方法中获取 AssetManager 引用。

这是一个读取 C++ 库中资产的 Flutter 插件示例: https://github.com/mikeperri/ndk_asset_manager_example/commit/533d28b33c1d22f89028f89691f78e907bf19db3

【讨论】:

    猜你喜欢
    • 2012-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多