最近开发了一款基于Android6.0系统的钢琴学习系统,本文将主要介绍MIDI协议的常识、Android MIDI 相关API的使用,以及MIDI应用程序开发需要借鉴的开源项目,最后分析本项目的架构。
本文的主要结构如下图所示:
1.MIDI概述
1.1乐器数字接口
(Musical Instrument Digital Interface,简称MIDI)是一个工业标准的电子通信协议.为电子乐器等演奏设备(如合成器)定义各种音符或弹奏码,容许电子乐器、电脑、手机或其它的舞台演出配备彼此连接,调整和同步,得以即时交换演奏数据。MIDI不发送声音,只发送像是音调和音乐强度的数据,音量,颤音和相位[1]等参数的控制信号,还有设置节奏的时钟信号。在不同的电脑上,输出的声音也因音源器不同而有差异。
1.2 MIDI的优点
1.共通语言及语法:
键盘乐器,电子鼓,电脑,编曲机及其他为MIDI设计出来的特殊功能电子乐器之间可以轻易的相互链接
2.简化的链接:
减少了音乐设备之间导线、信号线链接的复杂性(如音量控制)
3.更少的演出者:
1980年代初期,音乐演出者可以仅靠一至两人进行现场演出,同时操作数台MIDI设备,制造出像交响乐团般的演出效果
4.更低的获取门槛:
用户可以以更少的花费创作、编辑、制作高质量的数字音乐。专业的音乐家可以在家里自己录音,宅录,不用花钱租录音室,也不用请一堆乐手来帮忙录音。同时更让没音乐基础的爱好者可以利用MIDI音乐软件高度扩充性进行高质量录音
5.方便可携的电子音乐器材:
大量减少了乐手巡回演出时所需携带的乐器、器材与线材的数量,在搬运、装载、架设器材也简易了许多,却仍可以制造出相当的音色与效果.
1.3 MIDI的运作过程
当MIDI播放器演奏了一个音符的时候,它随之将音符转换成MIDI信息。一个典型的由键盘获取的音符的MIDI信息的过程包括:
1.用户以特定速率(又译,力度-velocity)演奏中央C音符。
2.用户改变按压键盘按键的力度-这个技术称为键后触感(aftertouch)。
3.用户释放并停止演奏中央C音符。
2.Android Midi相关API
2.1用到的主要类在android.media.midi中,除此之外还要用到音频播放相关的API:AudioTrack,AudioRecord等相关API.
2.2接口 MidiManager.OnDeviceOpenedListener
/** * Listener class used for receiving the results of {@link #openDevice} and * {@link #openBluetoothDevice} */ public interface OnDeviceOpenedListener { /** * Called to respond to a {@link #openDevice} request * * @param device a {@link MidiDevice} for opened device, or null if opening failed */ abstract public void onDeviceOpened(MidiDevice device); } 在调用Midimanger.openDevice(MidiDeviceInfo, MidiManager.OnDeviceOpenedListener, Handler)方法时需要传入这样一个接口,再回调函数里能够得到打开的设备MidiDevice
2.3相关类
2.3.1 MidiDevice该类属于Midi设备,该类的创建比较特殊,需要通过 openDevice(MidiDeviceInfo, MidiManager.OnDeviceOpenedListener, Handler)进行创建,在MidiManager.OnDeviceOpenedListener的回调中会得到该类1 MidiDeviceInfo getInfo ()2 MidiInputPort openInputPort (int portNumber)3 MidiOutputPort openOutputPort (int portNumber)这三个方法相对比较容易理解,主要用来得到设备的信息,通过端口号来打开一个输入端口和输出端口4 MidiDevice.MidiConnection connectPorts (MidiInputPort inputPort, int outputPortNumber)这个方法相对比较特殊,主要用来将两个设备连接起来,第一个参数为需要连接的设备的输入端口,第二个参数为输出设备的输出端口号
2.3.2 MidiDevice.MidiConnection两个设备连接在一起的connection当需要断开连接时需要关闭对应的connection ,释放流和相应的系统资源,防止内存泄漏等
2.3.3 MidiDeviceInfoMidiDeviceInfo 包含了设备的所有物理信息的详细描述,而真正在不通设备之间建立连接的是MidiDevice.MidiDeviceInfo包含丰富的物理信息:PROPERTY_BLUETOOTH_DEVICEPROPERTY_MANUFACTURERPROPERTY_NAMEPROPERTY_PRODUCTPROPERTY_SERIAL_NUMBERPROPERTY_USB_DEVICEPROPERTY_VERSIONTYPE_BLUETOOTHTYPE_USBTYPE_VIRTUAL可以通过下面的方法传入不同的常量来获得对应的信息info.getProperties() .getString(MidiDeviceInfo.PROPERTY_MANUFACTURER);int getId ():返回设备的id号int getInputPortCount ():得到设备的输入端口的个数int getOutputPortCount ()得到设备输出端口的个数PortInfo[] getPorts ():返回设备的端口 ,返回的变量是一个数组,可能包含设备的输入端口和输出端口,PortInfo是MidiDeviceInfo的内部类,包含端口的信息,输入输出类型等Bundle getProperties ():返回一个bundle对象,可以用其或得设备的物理信息int getType ():得到设备的类型boolean isPrivate ():判断设备是否属于私有设备
2.3.4 MidiDeviceInfo.PortInfoString getName ()int getPortNumber ()int getType ()几个方法很容易理解分别返回端口的名字 端口号 以及输入输出类型
2.3.5 MidiDeviceServiceMidiDeviceService 继承于Service ,用于创建一个虚拟的Midi设备一般情况下,我们通过继承这个类去构建一个虚拟的Midi设备,继承这个类必须重写两个方法,分别用来接收其他Midi设备发出的信息,或者向其他设备发送信息 MidiReceiver[] onGetInputPortReceivers (),向service设备提供MidiReceiver 去接收信息 MidiReceiver[] getOutputPortReceivers () ,用来提供发送信息的MidiReceiver并且继承这个类必须在Manifest文件中进行注册我们可以定义该虚拟设备的制造商,产品 以及输入输出类型等信息MidiDeviceInfo getDeviceInfo ()得到该设备的信息void onDeviceStatusChanged (MidiDeviceStatus status)在设备的状态被改变时进行回调
2.3.6 MidiDeviceStatus1.MidiDeviceInfo getDeviceInfo ()能够通过状态改变的回调,得到设备的信息2.int getOutputPortOpenCount (int portNumber)假如该设备是一个输出设备,能够得到所有与该输出设备某端口(portNumbe)连接的设备的数量,一个输出设备能够同时被多个设备连接。3.boolean isInputPortOpen (int portNumber)假如该设备是一个输入设备,能够得到该输入设备的某个端口是否打开,一个输入设备,只能被一个设备连接
2.3.7 MidiInputPort
2.3.8 MidiManagerMidiManger 是MIDI设备的管理者 ,类似于PackgerManger MediaManger 等系统某一类的管理者,其得到的方式与其他Manger的得到方式类似:MidiManager manager = (MidiManager) getSystemService(Context.MIDI_SERVICE);几个常用的公用方法:1 MidiDeviceInfo[] getDevices () 该方法能够返回 系统所连结的所有MIDI设备,系统能够识别多个不同的MIDI设备,甚至包括输入输出设备,并将这些设备的相关信息以数组的形式返回2 void openBluetoothDevice (BluetoothDevice bluetoothDevice, MidiManager.OnDeviceOpenedListener listener, Handler handler) 该方法表明系统可以连接通过蓝牙连接MIDI设备,假设该Midi设备支持蓝牙,因开发过程中没有此设备,没有做过测试,但蓝牙Midi设备的可扩展性、便携性无疑更强3 void openDevice (MidiDeviceInfo deviceInfo, MidiManager.OnDeviceOpenedListener listener, Handler handler) 该方法用来打开一个设备,需要传入被打开设备的信息 打开设备的回调接口 以及Handler4 void registerDeviceCallback (MidiManager.DeviceCallback callback, Handler handler) 注册一个回调接口,用来检测设备的插入与连接,当设备被连接时,如果该设备有打开的端口, onDeviceStatusChanged(MidiDeviceStatus)方法会立刻被调用.5 void unregisterDeviceCallback (MidiManager.DeviceCallback callback) 注销回调接口
2.3.9 MidiManager.DeviceCallback当设备被连接时的回调:void onDeviceAdded (MidiDeviceInfo device)void onDeviceRemoved (MidiDeviceInfo device)void onDeviceStatusChanged (MidiDeviceStatus status) 分别对应设备插入 移除 状态改变的 notifaction 通知
2.3.10 MidiOutputPort
2.3.11 MidiReceiverMidiReceiver 用来接收来自Midi设备的信息或者向Midi设备发送信息1.int getMaxMessageSize () 返回MidiReceiver可以接受的信息的最大容量2 void onSend (byte[] msg, int offset, int count, long timestamp)当信息被传递的时候被调用3 void send (byte[] msg, int offset, int count)用来发送信息(不包含发送时间)4 void send (byte[] msg, int offset, int count, long timestamp)用来发送信息(包含时间戳)
2.3.12MidiSenderMidiSender 用来绑定Midi设备的MidiReceiver1 void connect (MidiReceiver receiver)用来连接某个MidiReceiver
2.2.4相关开源项目
https://github.com/googlesamples/android-MidiScope.git(只发送信息,不发声音)https://github.com/googlesamples/android-MidiSynth.git(能够检测设备,连接设备并发声)