android - 在Android上使用Delphi实现MIDI

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

自一段时间以来,我一直在寻找一种以Android为目标的在Delphi XE5中播放MIDI的方法。我之前的几个问题都与此“任务”有关:-)。我向embarcadero提出了两个请求:#119422向TMediaPlayer添加MIDI支持,以及#119423向Firemonkey添加MIDI框架,但这没有帮助。我终于成功了。据我所知,还有更多人正在Android上寻找MIDI,我将这个问题与文档答案一起发布。

最佳答案

Android系统具有内部MIDI合成器。您可以通过Android NDK访问它。我已经在article containing some downloads中对此进行了描述。该答案是对本文的简短描述。您将在此处看到的是概念证明。它将显示如何在Android系统上播放MIDI音符,但需要改进。欢迎提出改进建议:-)

使用Eclipse与Java项目交互。我假设您有带有Mobile Pack的Delphi XE5,它为您提供了已经安装的两件东西:Android SDK和NDK。不要通过从Google下载完整的Android SDK重新安装它们。 Download and install Eclipse Android开发工具(ADT)插件,并按照安装说明进行操作。这使您可以使用Delphi XE5已安装的Android SDK / NDK环境(您可以在Delphi中的“选项” |“工具” |“ SDK管理器”中找到路径)。这样,Delphi和Eclipse将共享相同的SDK和NDK。

我使用了MIDI library developed by Willam Farmer。他还拥有完整的SoniVox文档,而我在其他地方找不到。他的驱动程序附带一个完整的示例(Java)程序。我使用创建了自己的项目,并将包名称更改为org.drivers.midioutput,因此所有函数均以Java_org_drivers_midioutput_MidiDriver_为前缀(请参见下面的代码)。

当您希望编译midi.c时,请打开命令窗口,然后在项目目录中调用ndk-build。某些错误消息是可以的。在我的情况下,没有构建mips和x86库。

有一点需要注意:ndk的路径不能包含空格。在让Delphi安装程序安装Delphi的过程中,肯定会有一个空格:该可怕的长文件名中的Rad Studio子目录,Delphi将在其中安装SDK和NDK。为了解决此问题,请在驱动器C:上创建一个空目录,将其命名为C:\ ndk。使用MKLINK将此目录链接到ndk目录。这只能在提升权限的命令提示符下完成,否则您将失去网络连接。该链接是持久的,因此只需关闭命令提示符并打开另一个未提升的命令,所有命令现在都可以正常工作。现在您可以真正使用ndk-build了。

midi.c-SoniVox的NDK接口

////////////////////////////////////////////////////////////////////////////////
//
//  MidiDriver - An Android Midi Driver.
//
//  Copyright (C) 2013  Bill Farmer
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//  Bill Farmer  william j farmer [at] yahoo [dot] co [dot] uk.
//
///////////////////////////////////////////////////////////////////////////////

// Some slight modifications by Arnold Reinders. Added a test function and changed
// the package to org.drivers.midioutput. The original copyright still applies 

#include 

// 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;

// This function is added to test whether the functionality of this NDK code can be accesses
// without needing to access the MIDI system. Added for testing purposes
jint
Java_org_drivers_midioutput_MidiDriver_version (JNIEnv *env, jobject clazz)
{
   return 3;
}

// 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;
}


当库由ndk-build构建时,它将在已编译的库之前加上lib前缀,并以.so代替扩展名。因此,midi.c将编译为libmidi.so。编译的库已添加到下载中,因此您无需编译midi.c。

MidiDriver.Java声明一个接口,一个audioTrack和一个线程来处理所有这些。我没有费力找到它是如何工作的。因为我不知道如何处理接口,所以在Delphi中,我为MidiDriver创建了Java包装器:MIDI_Output类。此类用于与Delphi进行接口。

MidiDriver类是Java与调用SoniVox函数的C函数之间的接口。 MIDI_Output类是Java和Delphi之间的接口。 MIDI_Output创建MidiDriver的实例。

MidiDriver类-与NDK的接口

////////////////////////////////////////////////////////////////////////////////
//
//  MidiDriver - An Android Midi Driver.
//
//  Copyright (C) 2013  Bill Farmer
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//  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

      Log.d ("midi", "   ***   processMIDI");
      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

    public  native int     version ();
    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");
    }
}


MIDI_Output类-为MidiDriver类提供包装

package org.drivers.midioutput;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;

import org.drivers.midioutput.MidiDriver.OnMidiStartListener;

import android.content.res.AssetFileDescriptor;
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
      midi_driver = new MidiDriver();

      Log.d ("midi", "   ***   midi_driver opened with version "  +
                     String.valueOf (midi_driver.version ()));

// Set onmidistart listener to this class

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

   public int test_int (int n)
   {
      int sq = n * n;

//      Log.d ("midi", "   ***   test_int computes " + String.valueOf (sq));
      return n * n;
   }

   public void start ()
   {
      if (midi_driver != null)
      {
         midi_driver.start ();
         Log.d ("midi", "   ***   midi_driver.start ()");
      }
   } // start //

   public void stop ()
   {
      if (midi_driver != null)
      {
         midi_driver.stop ();
         Log.d ("midi", "   ***   midi_driver.stop ()");
      }

      stopSong ();
   } // stop //

   // Listener for sending initial midi messages when the Sonivox
   // synthesizer has been started, such as program change. Runs on
   // the MidiDriver thread, so should only be used for sending midi
   // messages.

   @Override
   public void onMidiStart()
   {
      Log.d ("midi", "   ***   onSMidiStart");
  // TODO
   }

   // Sends a midi message

   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;

        Log.d ("midi", "   ***   putShort (" + String.valueOf (m) + ", " + String.valueOf (n) + ", " + String.valueOf (v) + ")");

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

   public boolean isPlayingSong ()
   {
      return media_player != null;
   } // isPlayingSong //

   public void playSong (String audioFilename)
   {
      String audioPath;

      try 
      {
         FileDescriptor fd = null;
         audioFilename = "/Data/d/song.mid";

         File baseDir = Environment.getExternalStorageDirectory ();
         audioPath = baseDir.getAbsolutePath () + audioFilename;

         Log.d ("midi", "   ***   Look for file: " + audioPath);

         FileInputStream fis = new FileInputStream (audioPath);
         fd = fis.getFD ();

         if (fd != null) 
         {
             Log.d ("midi", "   ***   Found file, trying to play: " + audioPath);
             MediaPlayer mediaPlayer = new MediaPlayer ();
             mediaPlayer.setDataSource (fd);
             mediaPlayer.prepare ();
             mediaPlayer.start ();
         }
     } catch (Exception e) 
     {
        Log.d ("midi", "   ***   Exception while trying to play file: " + e.getMessage ());
     }      
   }

   public void stopSong ()
   {
      if (media_player != null)
      {
         media_player.stop ();
         media_player.release ();
         media_player = null;
      } // if
   } // stopSong //
} // Class: MIDI_Output //


从MidiDriver和MIDI_Output创建了一个Eclipse Android项目,并添加了MainActivity并运行它。消除了许多错误之后,我开始运行它。一个有用的工具是android调试器(adb)。打开命令窗口,然后运行adb -d logcat。我在代码中添加了很多log.d(“ midi”,“ ***消息”)语句,以查看出错的地方。如果您不喜欢它们,可以将其删除,但是如果您不了解Android(我仍然在很大程度上),则可以使用它来查看应用程序中发生的情况。日志在Delphi中可正常使用,请参见Delphi源。

程序编译好后,您的project \ bin目录中将有一个MIDI_Output.apk包。 Delphi将使用此包来运行Java方法。

可以使用JNI从Delphi访问Java。可以在RedTitan的站点上找到动手教程。本教程的思想在类TMIDI_Output_Device中实现。

如您所见,常量字符串test_apk_fn是使用MIDI_Output.apk Android包的路径定义的。该字符串为JNI提供可以在其中找到Java库的名称。字符串javaClassName提供了与Java接口所必需的包名称。使用这些字符串,Delphi JNI能够找到请求的类。

TMIDI_Output_Device类-为Java类MIDI_Output提供Delphi包装

unit MIDI_Output_Device;

interface

uses
   System.SysUtils,
   FMX.Types,
   Androidapi.JNIBridge,
   Androidapi.JNI.JavaTypes,
   Androidapi.Jni,
   Androidapi.JNI.Dalvik,
   Androidapi.JNI.GraphicsContentViewText;

const
   test_apk_fn  = '/storage/sdcard0/Data/d/MIDI_Output.apk';

type
   TMIDI_Output_Device = class (TObject)
   private
      JavaEnv: PJNIEnv;
      context: JContext;
      CL: JDexClassLoader;
      JavaObject: JObject;
      JavaObjectID: JNIObject;
      jTempClass: Jlang_Class;
      jTemp: JObject;
      oTemp: TObject;
      jLocalInterface: ILocalObject;
      optimizedpath_jfile: JFile;
      dexpath_jstring, optimizedpath_jstring: JString;
      fun_version: JNIMethodID;
      fun_start: JNIMethodID;
      fun_put_short: JNIMethodID;
      fun_play_song: JNIMethodID;

   public
      constructor Create;
      procedure setup_midi_output (class_name: string);
      procedure put_short (status, data_1, data_2: integer);
      procedure play_song (file_name: string);
   end; // Class: MIDI_Output_Device //

implementation

uses
   FMX.Helpers.Android;

constructor TMIDI_Output_Device.Create;
begin
   setup_midi_output ('MIDI_Output');
end; // Create //

procedure TMIDI_Output_Device.setup_midi_output (class_name: string);
var
   javaClassName: string;
   ji: JNIInt;
   jiStatus, jiData_1, jiData_2: JNIValue;

begin
   javaClassName := Format ('org.drivers.midioutput/%s', [class_name]);
   context := SharedActivityContext;
   JavaEnv := TJNIResolver.GetJNIEnv;

   Log.d ('Loading external library from "' + test_apk_fn + '"');
   dexpath_jstring := StringToJString (test_apk_fn);

// locate/create a directory where our dex files can be put
   optimizedpath_jfile := context.getDir (StringToJString ('outdex'), TJContext.javaclass.mode_private);
   optimizedpath_jstring := optimizedpath_jfile.getAbsolutePath;

   Log.d ('Path for DEX files = ' + JStringToString (optimizedpath_jstring));
   Log.d ('APK containing target class = ' + JStringToString (dexpath_jstring));

   CL := TJDexClassLoader.JavaClass.init (dexpath_jstring, optimizedpath_jstring, nil, TJDexClassLoader.JavaClass.getSystemClassLoader);

// Test whether the Dex class is loaded, if not, exit
   if not assigned (CL) then
   begin
      Log.d ('?Failed to get DEXClassLoader');
      exit;
   end; // if

// Load the Java class
   jTempClass := CL.loadClass (StringToJString (javaClassName));
   if assigned (jTempClass) then
   begin
      jTemp := jTempClass;    // N.B You could now import the entire class
      if jTemp.QueryInterface (ILocalObject,jLocalInterface) = S_OK then
      begin
         // supports ilocalobject
         JavaObject := jTempClass.newInstance;
         oTemp := JavaObject as TObject;
         JavaObjectID := tjavaimport (otemp).GetObjectID;
         Log.d (oTemp.ClassName);

// try to access the version function from the midi_output class
         fun_version := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'version', '()I');
         if not assigned (fun_version) then
         begin
           Log.d ('?fun_version not supported');
         end else
         begin
            ji := JavaEnv^.CallIntMethodA (JavaEnv, JavaObjectID, fun_version, nil);
            Log.d ('version returns ' + inttostr (ji));
         end; // if

// try to access the start function from the midi_output class
         fun_start := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'start', '()V');
         if not assigned (fun_start) then
         begin
           Log.d ('?fun_start not supported');
         end else
         begin
            JavaEnv^.CallVoidMethodA (JavaEnv, JavaObjectID, fun_start, nil);
            Log.d ('fun_start found');
         end; // if

// try to access the putShort function from the midi_output class
         fun_put_short := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'putShort','(III)V');
         if not assigned (fun_put_short) then
         begin
            Log.d ('?putShort not supported');
         end else
         begin
            Log.d (Format ('   @@@   putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
            put_short ($90, 60, 127);
         end; // if

// try to access the playSong function from the midi_output class
         fun_play_song := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'playSong', '(Ljava/lang/String)V');
         if not assigned (fun_play_song) then
         begin
            Log.d ('?playSong not supported');
         end else
         begin
            Log.d ('   @@@   playSong found');
         end; // if
      end else
      begin
         Log.d ('?Could not derive ILOCALOBJECT');
      end;
   end else Log.d ('?'+javaClassname+' not found')
end; // setup_midi_output //

procedure TMIDI_Output_Device.put_short (status, data_1, data_2: integer);
var
   jiStatus, jiData_1, jiData_2: JNIValue;
   x: array of JNIOBJECT;

begin
   jiStatus.i := status;
   jiData_1.i := data_1;
   jiData_2.i := data_2;
   setLength (x, 3);
   x [0] := jiStatus.l;
   x [1] := jiData_1.l;
   x [2] := jiData_2.l;
   Log.d (Format ('putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
   JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_put_short, x);
end; // put_short //

procedure TMIDI_Output_Device.play_song (file_name: string);
var
   x: array of JNIObject;
begin
   SetLength (x, 1);
   x [0] := StringToJNIString (JavaEnv, file_name);
   Log.d ('playSong (' + file_name + ')');
   JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_play_song, x);
end; // playSong //

end. // Unit: MIDI_Output_Device //


现在,Delphi知道在哪里可以找到Java类。从理论上讲,它现在应该能够找到libmidi.so,因为Android软件包是一个.zip文件,其中包含运行Java软件包所需的文件。如果使用WinZip或WinRar打开MIDI_Output.apk,则会看到这些文件。在档案中,您将找到目录lib,其中包含用于ARM 5和7平台的libmidi.so。启动程序并在命令窗口中运行adb -d logcat时,adb表示要解压缩MIDI_Output.apk。好吧,它可能会这样做,但是找不到libmidi.so。

Libmidi.so应该添加到Android SDK的\ platforms目录下的\ usr \ lib中。在我的情况下,完整的链接是:C:\ Users \ Public \ Documents \ RAD Studio \ 12.0 \ PlatformSDKs \ android-ndk-r8e \ platforms \ android-14 \ arch-arm \ usr \ lib。这对我found out some time ago应该有帮助。

使用我在此处显示的调用链,可以在Delphi生成的Android代码中调用MIDI函数。关于此技术存在一些问题:


直接调用NDK函数会更容易吗?它是
可能在同一call NDK functions directly from Delphi
DLL的方式。但是,类MidiDriver增加了很多功能
我目前不了解。此功能必须是
直接调用NDK函数时,用C或Pascal编程。
在Bill Farmer的代码中,他使用MediaPlayer播放MIDI
文件。 las MediaPlayer只能从Activity和
我不知道如何将Delphi MainActivity转移到JNI Java
功能。因此,该功能目前尚无法使用。
本机库打包到.apk中,但不能以这种方式解包
JavaVM检测它的方式。现在必须将libmidi.so放进去
手动进入\ usr \ lib。
更糟糕的是,必须将硬链接添加到.apk包中。的
软件包应自动部署到以下位置的/ data / app-lib
应用程序,否则使用JNI类创建一个应用程序并安装它
无法从Play商店购买。

关于android - 在Android上使用Delphi实现MIDI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21148723/

相关文章:

Android XML : android:elevation vs. 应用程序:海拔

德尔福XE2 : How to pass a field with null value?

android - 如何设置 GNUMAKE 变量以使 ndk-build 正常工作

android - 如何使用原生库

c++ - 在 Android NDK 项目中包含 ICU

android - Android Room 数据库的几种不同崩溃 - 不知道为什么

android - 如何自定义 PagerTitleStrip android

android - ACTION_DOWN、ACTION_UP 和 onClick 事件

delphi - 向枚举类型添加新元素

delphi - 未使用的接口(interface)引用不会被销毁