gpt4 book ai didi

android - 在Android上使用Delphi实现MIDI

转载 作者:行者123 更新时间:2023-12-03 15:19:06 25 4
gpt4 key购买 nike

自一段时间以来,我一直在寻找一种以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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com