Delphi / Android在哪里搜索本地语言库?

时间:2014-01-07 21:57:06

标签: java delphi android-ndk java-native-interface

我想为Delphi Android应用添加MIDI功能。 MIDI可通过SoniVox库获得,可通过Android NDK访问。可以找到此驱动程序的示例here。驱动程序是用C语言编写的,使用NDK可以创建一个可以通过System.loadLibrary调用访问的本机语言库。

  //  MidiDriver - An Android Midi Driver.
  //  Copyright (C) 2013    Bill Farmer
  //  Bill Farmer    william j farmer [at] yahoo [dot] co [dot] uk.

  #include <jni.h>

  // for EAS midi
  #include "eas.h"
  #include "eas_reverb.h"

  // determines how many EAS buffers to fill a host buffer
  #define NUM_BUFFERS 4

  // EAS data
  static EAS_DATA_HANDLE pEASData;
  const S_EAS_LIB_CONFIG *pLibConfig;
  static EAS_PCM *buffer;
  static EAS_I32 bufferSize;
  static EAS_HANDLE midiHandle;

  // init EAS midi
  jint
  Java_org_drivers_midioutput_MidiDriver_init(JNIEnv *env,
                          jobject clazz)
  {
      EAS_RESULT result;

      // get the library configuration
      pLibConfig = EAS_Config();
      if (pLibConfig == NULL || pLibConfig->libVersion != LIB_VERSION)
    return 0;

      // calculate buffer size
      bufferSize = pLibConfig->mixBufferSize * pLibConfig->numChannels *
    NUM_BUFFERS;

      // init library
      if ((result = EAS_Init(&pEASData)) != EAS_SUCCESS)
    return 0;

      // select reverb preset and enable
      EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET,
             EAS_PARAM_REVERB_CHAMBER);
      EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS,
             EAS_FALSE);

      // open midi stream
      if (result = EAS_OpenMIDIStream(pEASData, &midiHandle, NULL) !=
    EAS_SUCCESS)
      {
    EAS_Shutdown(pEASData);
    return 0;
      }

      return bufferSize;
  }

  // midi config
  jintArray
  Java_org_drivers_midioutput_MidiDriver_config(JNIEnv *env,
                            jobject clazz)
  {
      jboolean isCopy;

      if (pLibConfig == NULL)
    return NULL;

      jintArray configArray = (*env)->NewIntArray(env, 4);

      jint *config = (*env)->GetIntArrayElements(env, configArray, &isCopy);

      config[0] = pLibConfig->maxVoices;
      config[1] = pLibConfig->numChannels;
      config[2] = pLibConfig->sampleRate;
      config[3] = pLibConfig->mixBufferSize;

      (*env)->ReleaseIntArrayElements(env, configArray, config, 0);

      return configArray;
  }

  // midi render
  jint
  Java_org_drivers_midioutput_MidiDriver_render(JNIEnv *env,
                            jobject clazz,
                            jshortArray shortArray)
  {
      jboolean isCopy;
      EAS_RESULT result;
      EAS_I32 numGenerated;
      EAS_I32 count;
      jsize size;

      // jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy)
      // void ReleaseByteArrayElements(jbyteArray array, jbyte* elems,

      // void* GetPrimitiveArrayCritical(JNIEnv*, jarray, jboolean*);
      // void ReleasePrimitiveArrayCritical(JNIEnv*, jarray, void*, jint);

      if (pEASData == NULL)
    return 0;

      buffer =
    (EAS_PCM *)(*env)->GetShortArrayElements(env, shortArray, &isCopy);

      size = (*env)->GetArrayLength(env, shortArray);

      count = 0;
      while (count < size)
      {
    result = EAS_Render(pEASData, buffer + count,
                pLibConfig->mixBufferSize, &numGenerated);
    if (result != EAS_SUCCESS)
        break;

    count += numGenerated * pLibConfig->numChannels;
      }

      (*env)->ReleaseShortArrayElements(env, shortArray, buffer, 0);

      return count;
  }

  // midi write
  jboolean
  Java_org_drivers_midioutput_MidiDriver_write(JNIEnv *env,
                           jobject clazz,
                           jbyteArray byteArray)
  {
      jboolean isCopy;
      EAS_RESULT result;
      jint length;
      EAS_U8 *buf;

      if (pEASData == NULL || midiHandle == NULL)
    return JNI_FALSE;

      buf = (EAS_U8 *)(*env)->GetByteArrayElements(env, byteArray, &isCopy);
      length = (*env)->GetArrayLength(env, byteArray);

      result = EAS_WriteMIDIStream(pEASData, midiHandle, buf, length);

      (*env)->ReleaseByteArrayElements(env, byteArray, buf, 0);

      if (result != EAS_SUCCESS)
    return JNI_FALSE;

      return JNI_TRUE;
  }

  // shutdown EAS midi
  jboolean
  Java_org_drivers_midioutput_MidiDriver_shutdown(JNIEnv *env,
                              jobject clazz)
  {
      EAS_RESULT result;

      if (pEASData == NULL || midiHandle == NULL)
    return JNI_FALSE;

      if ((result = EAS_CloseMIDIStream(pEASData, midiHandle)) != EAS_SUCCESS)
      {
    EAS_Shutdown(pEASData);
    return JNI_FALSE;
      }

      if ((result = EAS_Shutdown(pEASData)) != EAS_SUCCESS)
    return JNI_FALSE;

      return JNI_TRUE;
  }

我使用Eclipse创建了一个Android应用程序,将MidiDriver添加为本机库,并使所有内容都正常运行。有了这个驱动程序,我的应用程序中有MIDI功能。您可以在下面找到MidiDriver代码的大纲。

  //  MidiDriver - An Android Midi Driver.
  //  Copyright (C) 2013    Bill Farmer
  //  Bill Farmer    william j farmer [at] yahoo [dot] co [dot] uk.

  package  org.drivers.midioutput;

  import java.io.File;

  import android.media.AudioFormat;
  import android.media.AudioManager;
  import android.media.AudioTrack;
  import android.util.Log;

  // MidiDriver

  public class MidiDriver implements Runnable
  {
      private static final int SAMPLE_RATE = 22050;
      private static final int BUFFER_SIZE = 4096;

      private Thread thread;
      private AudioTrack audioTrack;

      private OnMidiStartListener listener;

      private short buffer[];

      // Constructor

      public MidiDriver ()
      {
         Log.d ("midi", ">> MidiDriver started");
      }

      public void start ()
      {
    // Start the thread
         thread = new Thread (this, "MidiDriver");
         thread.start ();
      } // start //

      @Override
      public void run ()
      {
          processMidi ();
      } // run //

      public void stop ()
      {
        Thread t = thread;
        thread = null;

        // Wait for the thread to exit

        while (t != null && t.isAlive ())
            Thread.yield ();
      } // stop //

      // Process MidiDriver

      private void processMidi ()
      {
        int status = 0;
        int size = 0;

        // Init midi

        if ((size = init()) == 0)
            return;

        buffer = new short [size];

        // Create audio track

        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE,
                        AudioFormat.CHANNEL_OUT_STEREO,
                        AudioFormat.ENCODING_PCM_16BIT,
                        BUFFER_SIZE, AudioTrack.MODE_STREAM);
        if (audioTrack == null)
        {
            shutdown ();
            return;
        } // if

        // Call listener

        if (listener != null)
            listener.onMidiStart();

        // Play track

        audioTrack.play();

        // Keep running until stopped

        while (thread != null)
        {
            // Render the audio

            if (render(buffer) == 0)
               break;

            // Write audio to audiotrack

            status = audioTrack.write(buffer, 0, buffer.length);

            if (status < 0)
               break;
        } // while

        // Render and write the last bit of audio

        if (status > 0)
            if (render(buffer) > 0)
               audioTrack.write(buffer, 0, buffer.length);

        // Shut down audio

        shutdown();
        audioTrack.release();
      } // processMidi //

      public void setOnMidiStartListener (OnMidiStartListener l)
      {
         listener = l;
      } // setOnMidiStartListener //

      public static void load_lib (String libName) 
      {
         File file = new File (libName);

         if (file.exists ()) 
         {
             System.load (libName);
         } else 
         {
             System.loadLibrary (libName);
         }
     }    // Listener interface

      public interface OnMidiStartListener
      {
         public abstract void onMidiStart ();
      } // OnMidiStartListener //

      // Native midi methods

      private native int     init ();
      public  native int []  config ();
      private native int     render (short a []);
      public  native boolean write (byte a []);
      private native boolean shutdown ();

      // Load midi library

      static
      {
         System.loadLibrary ("midi");
      }
  }

接下来,我测试了JNI接口,看看我是否可以访问Java classes from Delphi via JNI。也没问题。所以理论上我应该能够通过Java接口访问MidiDriver:很酷!我将MidiDriver包装在另一个Java类中:MIDI_Output以便在内部处理接口(我不知道如何在Delphi中连接Java接口.MIDI_Output创建一个MidiDriver实例并在必要时从MidiDriver调用函数。这一切都很有用从Eclipse运行。下面的MIDI_Output的一些部分:

  package org.drivers.midioutput;

  import java.io.IOException;

  import org.drivers.midioutput.MidiDriver.OnMidiStartListener;

  import android.media.MediaPlayer;
  import android.os.Environment;
  import android.util.Log;

  public class MIDI_Output implements OnMidiStartListener
  {
     protected MidiDriver midi_driver;
     protected MediaPlayer media_player;

     public MIDI_Output ()
     {
        // Create midi driver
        Log.d ("midi", ">> Before initializing MIDI driver version 1");

        midi_driver = new MidiDriver();

  // Set onmidistart listener to this class

        if (midi_driver != null)
            midi_driver.setOnMidiStartListener (this);
     } // MIDI_Output () //

  protected void putShort (int m, int n, int v)
  {
     if (midi_driver != null)
     {
       byte msg [] = new byte [3];

       msg [0] = (byte) m;
       msg [1] = (byte) n;
       msg [2] = (byte) v;

       midi_driver.write (msg);
     } // if
  } // putShort //

  // and much more code
  // ...   

在上面的示例中,putShort从MidiDriver调用函数write,这是在本机库中定义的函数。这一切在Java中运行良好,但在戴尔的实践中,您可能已经猜到了一点点。为了更详细地显示调用链,我需要在Delphi中使用这个完整的装置,请参见下图。

enter image description here

libsonivox(在/system/lib中找到)中可以找到函数EAS_WriteMidiStream,该函数从write中的函数libmidi.so调用(在任何地方都可找到,但是也在/system/lib/vendor/lib)中,MidiDriver.javaMIDI_Output.apk中声明,MIDI_Output.javanew MidiDriver调用,创建midi_driver.write (...)并引用putShort在同一个包的函数putShort中。最后应该在Delphi中调用MIDI_Output,但它永远不会到达那里。

new MidiDriver创建试图加载midi库的midi时,它会中止。该程序无法加载库“adb -d logcat”。我跑 D/dalvikvm( 5251): DexOpt: --- BEGIN 'MIDI_Output.apk' (bootstrap=0) --- D/dalvikvm( 5320): DexOpt: load 50ms, verify+opt 174ms, 1050124 bytes D/dalvikvm( 5251): DexOpt: --- END 'MIDI_Output.apk' (success) --- D/dalvikvm( 5251): DEX prep '/storage/emulated/legacy/Data/d/MIDI_Output.apk': u nzip in 14ms, rewrite 401ms W/dalvikvm( 5251): dvmFindClassByName rejecting 'org.drivers.midioutput/MIDI_Out put' D/midi ( 5251): >> Before initializing MIDI driver version 1 W/dalvikvm( 5251): Exception Ljava/lang/UnsatisfiedLinkError; thrown while initi alizing Lorg/drivers/midioutput/MidiDriver; W/System.err( 5251): java.lang.UnsatisfiedLinkError: Couldn't load midi from loa der dalvik.system.DexClassLoader[DexPathList[[zip file "/storage/sdcard0/Data/d/ MIDI_Output.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]: findLib rary returned null W/System.err( 5251): at java.lang.Runtime.loadLibrary(Runtime.java:358) W/System.err( 5251): at java.lang.System.loadLibrary(System.java:526) W/System.err( 5251): at org.drivers.midioutput.MidiDriver.<clinit>(MidiDriver .java:177) W/System.err( 5251): at org.drivers.midioutput.MIDI_Output.<init>(MIDI_Output .java:22) W/System.err( 5251): at java.lang.Class.newInstanceImpl(Native Method) W/System.err( 5251): at java.lang.Class.newInstance(Class.java:1208) W/System.err( 5251): at dalvik.system.NativeStart.run(Native Method) D/dalvikvm( 5251): GC_FOR_ALLOC freed 795K, 9% free 9091K/9920K, paused 13ms, total 13ms D/dalvikvm( 5251): GC_CONCURRENT freed 9K, 4% free 9532K/9920K, paused 2ms+2ms, total 22ms 看看会发生什么,输出如下所示。 android屏幕上显示的错误消息将突出显示。

/vendor/lib

我知道找不到图书馆。麻烦的是,我不知道这个库的搜索位置。错误消息提到/system/libcom.embarcadero.MIDI_Output_Project/lib。我将libmidi.so添加到这些库中。我将库添加到应用程序目录/storage/sdcard0/Data/d/(这是android存储库的地方),添加到libmidi.so(存储包含Java类的MIDI_Output.apk的目录)。我实际上用libmidi.so撒了我的android系统。我也尝试加载 package org.drivers.midioutput; import android.util.Log; public class class_test { public int test_int (int n) { int sq = n * n; String mess = "*** test_int computes " + String.valueOf (sq); Log.d ("midi", mess); return n * n; } }

作为测试,我在MIDI_Output包中添加了一个非常简单的示例类,并调用了函数test_int。这没有任何问题。

{{1}}

我的问题是:DalvikVM在哪个目录中寻找上述设置中的本机库(Delphi通过JNI调用Java类,Java通过NDK调用C库)?

第二个问题:这是一个图书馆搜索路径问题吗?也许Delphi无法通过JNI调用NDK。

我尽量做到尽可能简短。如果有人认为我应该添加更多代码,请告诉我。非常感谢任何帮助。

1 个答案:

答案 0 :(得分:1)

Delphi使用的NDK库应放在<path to your PlatformSDKs>\android-ndk-r8e\platforms\android-14\arch-arm\usr\lib中。我意识到这要归功于Arioch和Chris的建议直接使用NDK。 Source\rtl\android目录中的大多数android文件都包含文件Androidapi.inc,其中包含以下定义

const
    AndroidLib            = '/usr/lib/libandroid.so';
    AndroidJniGraphicsLib = '/usr/lib/libjnigraphics.so';
    AndroidEglLib         = '/usr/lib/libEGL.so';
    AndroidGlesLib        = '/usr/lib/libGLESv1_CM.so';
    AndroidGles2Lib       = '/usr/lib/libGLESv2.so';
    AndroidLogLib         = '/usr/lib/liblog.so';
    AndroidOpenSlesLib    = '/usr/lib/libOpenSLES.so';

Nexus-7上不存在目录/usr/lib,但我在上面提到的路径中找到了const部分中声明的所有文件。将libmidi.so复制到此目录可解决此问题。我现在有一个听不到声音的小问题。我现在试着解决这个问题,并尝试直接调用NDK。