zoukankan      html  css  js  c++  java
  • Java Sound初探

    网上关于java sound的正规资源讲解的非常好,本文不再给出示例,主要提供一些好的资源,并说说我的一些理解,用于形成对java sound的整体认识.

    一.几个词汇

    • TTS:text-to-speech,文本到语音转换
    • OCR:optical-character-recignition光学字符识别
    • MIDI:Musical Instrument Digital Interface,乐器数字化接口

    MIDI是20世纪80年代初由Dave Smith提出的,目的是解决电声乐器之间的通信.现代音乐都是通过MIDI+音色库合成的.MIDI传输的不是声音信号而是一系列音符控制参数等指令,它告诉MIDI设备要做什么.MIDI传输的信号被统一成MIDIMessage,通过异步串行通信来传递.

    • Tritonus:java sound是一种标准,有两套实现.一套是Sun公司的,一套是Tritonus.在Java 1.3中,Sun公司的被纳入Java标准库.从那时起,Tritonus就很尴尬了.要想使用Tritonus就需要禁用掉Sun的,而禁用Sun的是一件多此一举的事情.Tritonus目前只支持Linux系统,但Tritonus的一些单独下载的插件也可以运行在其他系统上.

    • SPI:Service Provider Interface服务提供接口,这是API中常见的一种模式.把代码写成接口的形式,一些服务可以遵照这些接口来实现,从而实现可插拔式的编程.SPI还有另外一个意思:在电路中,SPI指串行外设接口,Serial Peripheral Interface,它是一种高速全双工,同步的通信总线.

    • Acousitic声学,Reverb回响,Gain增益,Pan声象.DAC(digital analog converter)数模转换器.

    二.学习资源

    jsresource

    js指java sound,这个网站专门讲解java sound,包罗万象,堪称java sound的百科全书,有这一个网站就足够了,现在需要做的就是把这个网站从头看到尾.

    oracle官网上的java sound介绍

    官网一向都是最重要的文档提供者,在oracle官网上,有一个详尽强大的样例,它展示了java sound的各个方面.从这个页面上可以下载样例.这个样例特别好,竟然可以用来弹奏钢琴,充分展示了java sound的强大功能.

    http://www.oracle.com/technetwork/java/javase/downloads/index.html

    这个页面也是Oracle官网页面,是jdk下载页面.在这个页面中,有jdk的demo,doc的下载链接,这些都是学习java的上好资源,下载下来,仔细阅读javax.sound模块.

    http://www.javazoom.net/index.shtml

    java sound直接支持的音频格式非常少,只包括.wav(多见于windows)和.AIFF(多见于macintosh)和.au(多见于unix)三种格式的音频文件.但通过SPI,我们不须修改java代码,只需要提供相应格式的SPI就能够实现播放多种文件.javazoom网站提供了一套mp3解码库,名字叫做JLayer.
    Java Zoom网站上有多个关于java音频的项目,这里主要介绍JLayer和MP3SPI和jlGUI.JLayer于1999年2月启动,目标是为Java提供实时的MP3解码器.它还包括JLayerME子项目,是JLayer在JavaME上的版本.MP3SPI是一个基于JLayer和Tritonus的Java插件,Tritonus是java sound标准的另一种实现,要想使用MP3SPI,需要三个jar包:mp3spi.jar和tritonus.jar和jlayer.jar,将这三个jar包放到类路径下,java sound便具备了播放MP3的能力.jlGUI是一个图形界面的音乐播放器,它纯粹用Java写成,依赖于MP3SPI,这个音乐播放器简洁简陋,用着还行.

    http://www.sauronsoftware.it/projects/jave/manual.php

    jave(java audio video encoder)是一个纯java版的音视频转码器.

    三.概述

    AudioSystem是javax.sound包的重要入口类,一切都是以它为中心展开的,AudioSystem的默认输入设备是麦克风,默认输出设备是扬声器
    SourceDataLine和TargetDataLine都可以通过AudioSystem获得.SourceDataLine意思是"源数据流",是指AudioSystem的输入流,把音频文件写入到AudioSystem中,AudioSystem就会播放音频文件.TargetDataLine意思是"目标数据流",是指AudioSystem的输出流,是AudioSystem的target.所以,当播放文件时,把文件内容写入AudioSystem的SourceDataLine;当录音时,把AudioSystem的TargetDataLine中的内容读入内存.
    Clip是"剪辑","片段",表示内存中的一段完整的音频数据,可以一遍一遍的播放,非常适合播放游戏的背景音乐.Clip和SourceDataLine都是AudioSystem的输入端口.
    在java中处理声音的包括四个包:

    • javax.sound.sample处理数字音频
    • javax.sound.midi处理midi形式的音频
    • javax.sound.sample.spi相当于sample类型的服务提供接口
    • javax.sound.midi.spi相当于midi类型的服务提供接口

    四.最简单的播放器

    AudioInputStream cin = AudioSystem.getAudioInputStream(new File("haha.wav"));
    AudioFormat format = cin.getFormat();
    DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
    SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
    line.open(format);//或者line.open();format参数可有可无
    line.start();
    int nBytesRead = 0;
    byte[] buffer = new byte[512];
    while (true) {
        nBytesRead = cin.read(buffer, 0, buffer.length);
        if (nBytesRead <= 0)
            break;
        line.write(buffer, 0, nBytesRead);
    }
    line.drain();
    line.close();
    

    这个程序只能播放wav,pcm文件,不能播放mp3文件.
    第一步,从文件对象构建AudioInputStream cin,这个cin对象包含文件的格式数据和音频数据.
    第二步,根据cin的AudioFormat创建DataLine.Info对象.
    第三步,根据AudioFormat来获取SourceDataLine,扬声器有了SourceDataLine就有了数据流,扬声器就可以发声了.

    SourceDataLine像一个管道一样,数据流从计算机内部的音频文件中流到AudioSystem音频系统中,line.open()打开管道入口端,line.start()打开管道的出口端.line.drain()将管道的出口端导入另一个地方将管道中剩余的数据流放空.line.close()关住了管道的出口端.

    以上代码中有一个地方可以化简一下:

          //DataLine.Info info = new DataLine.Info(SourceDataLine.class, ais.getFormat());
          //SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);
          SourceDataLine sourceDataLine = AudioSystem.getSourceDataLine(ais.getFormat());
    

    第一种写法是:
    先根据audioInputStream#format获取DataLine.Info,然后由DataLine.Info获取SourceDataLine,这种方法写起来啰嗦。
    第二种写法直接通过audioInputStream#format构造SourceDataLine

    五.最简单的录音机

    File outputFile = new File("recoder.wav");
    AudioFormat audioFormat = new AudioFormat(
            AudioFormat.Encoding.PCM_SIGNED, 44100.0F, 16, 2, 4, 44100.0F,
            false);
    DataLine.Info info = new DataLine.Info(TargetDataLine.class,
            audioFormat);
    TargetDataLine targetDataLine = (TargetDataLine) AudioSystem
            .getLine(info);
    targetDataLine.open(audioFormat);
    targetDataLine.start();
    new Thread() {
        public void run() {
            AudioInputStream cin = new AudioInputStream(targetDataLine);
            try {
                AudioSystem.write(cin, AudioFileFormat.Type.WAVE,
                        outputFile);
                System.out.println("over");
            } catch (IOException e) {
                e.printStackTrace();
            }
        };
    }.start();
    System.out.println("录音已经开始,说完话之后,请输入回车键结束录音");
    System.in.read();
    targetDataLine.close();
    

    跟SourceDataLine的获取一样,要想从AudioSystem中获取TargetDataLine,需要规定好AudioFormat.
    AudioInputStream有两个构造函数:

    • AudioInputStream(TargetDataLine line)
    • AudioInputStream(InputStream stream, AudioFormat format, long length)

    重点说说第二个函数,InputStream stream指的是音频的数据流,AudioFormat format指的是音频的形式,length表示stream中包含多少个frame,计算方法是stream.length/frameSize,其中frameSize表示一个frame占用的字节数.frameSize=channelCount*sampleSize,一个frame记录的是当前时刻各个声道的音频值.采样率指的是一秒钟在某个声道的取样数,也就是一秒钟内的frame数.sampleRate=frameRate.

    AudioSystem.write()有两个重载函数:

    • write(AudioInputStream stream,AudioFileFormat.Type fileType, File out):写入到文件
    • write(AudioInputStream stream, AudioFileFormat.Type fileType, OutputStream out):写入到OutputStream

    AudioSystem.write()函数是线程阻塞的,只要AudioInputStream没有结束,就会一直等待输入.所以必须另开一个线程来录音,否则就无法关闭录音了.

    综合使用录音机和播放器,实现一个Repeater,你说啥电脑说啥。

    AudioFormat format = new AudioFormat(Encoding.PCM_SIGNED, 8000, 16, 1, 2, 8000, true);
    TargetDataLine dataLine = AudioSystem.getTargetDataLine(format);
    dataLine.open();
    dataLine.start();
    SourceDataLine sourceDataLine = AudioSystem.getSourceDataLine(format);
    sourceDataLine.open();
    sourceDataLine.start();
    while (true) {
    	byte[] buf = new byte[1024];
    	int cnt = dataLine.read(buf, 0, buf.length);
    	sourceDataLine.write(buf, 0, cnt);
    }
    

    六.使用Clip循环播放的小段音频

        public static void main(String[] args) throws Exception {
            Clip clip = AudioSystem.getClip();
            clip.open(AudioSystem.getAudioInputStream(new File("haha.wav")));
            clip.start();
            clip.setLoopPoints(0, clip.getFrameLength() - 1);
            while (true) {
    
            }
        }
    

    AudioSystem就像一个芯片,它有三种引脚:SourceDataLine,TargetDataLine,Clip.这三个东西都是继承自DataLine接口的接口.SourceDataLine用于播放音频,TargetDataLine用于录音,Clip用于循环播放一段音频,可以设置循环次数等.
    使用Clip播放音频时,会开启一个线程去播放音频,所以Clip是不阻塞的,这一点跟SourceDataLine不同.所以这个程序末尾写了一个死循环.
    Clip的获取方式除了直接从AudioSystem.getClip(),AudioSystem.getClip(Info)直接获取,还可以像SourceDataLine,TargetDataLine那样获取.

    AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(clipFile);
    AudioFormat    format = audioInputStream.getFormat();
    DataLine.Info    info = new DataLine.Info(Clip.class, format);
    Clip clip = (Clip) AudioSystem.getLine(info);
    

    七.播放MP3音频

    播放非pcm格式的音频时,必须有对应的解码器将相应格式转化为pcm格式才能够播放.pcm格式有三种:PCM_FLOAT,PCM_SIGNED,PCM_UNSIGNED.
    JLayer是一款MP3解码器,MP3SPI是基于JLayer和Tritonus的一款MP3 service provider interface.将jlayer.jar和mp3spi.jar和tritonus.jar三个jar包放到classpath中,解码时会自动查找相应的解码器进行解码.本示例程序依赖上述三个jar包.

            AudioInputStream stream = AudioSystem
                    .getAudioInputStream(new File("haha.mp3"));
            AudioFormat format = stream.getFormat();
            if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) {
                format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                        format.getSampleRate(), 16, format.getChannels(),
                        format.getChannels() * 2, format.getSampleRate(), false);
                stream = AudioSystem.getAudioInputStream(format, stream);
            }
            DataLine.Info info = new DataLine.Info(SourceDataLine.class,
                    stream.getFormat());
            SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem
                    .getLine(info);
            sourceDataLine.open(stream.getFormat(), sourceDataLine.getBufferSize());
            sourceDataLine.start();
            int numRead = 0;
            byte[] buf = new byte[sourceDataLine.getBufferSize()];
            while ((numRead = stream.read(buf, 0, buf.length)) >= 0) {
                int offset = 0;
                while (offset < numRead) {
                    offset += sourceDataLine.write(buf, offset, numRead - offset);
                }
                System.out.println(sourceDataLine.getFramePosition() + " "
                        + sourceDataLine.getMicrosecondPosition());
            }
            sourceDataLine.drain();
            sourceDataLine.stop();
            sourceDataLine.close();
            stream.close();
    

    先获取原文件的AudioFormat,然后根据这个旧的AudioFormat创建一个新的AudioFormat,注意新的AudioFormat除了Encoding发生改变,sampleSizeInBits也要改成16,相应的frameSize也要发生变化.frameSize表示一个frame占用的字节数,frameSize=channelCount*sampleSizeInBytes,也就是声道数channelCount*2.
    构建完了新的AudioFormat,通过AudioSystem.getAudioInputStream(format,stream)这个函数就能完成解码工作,这个函数会调用解码器进行解码.一旦解码完成,就会获得一个新的AudioInputStream,就可以像播放普通的wav文件一样进行播放了.
    在上面使用byte[]buf数组进行读取的过程中使用了两重循环,为什么呢?第一重是必需的,里面的第二重是怕sourceData里面写不完buf,如果只写一次有可能造成数据丢失.
    在上面播放过程中,不停地输出播放的frame数和播放的毫秒数,这是为了展示SourceDataLine的这两个函数.
    转码的另一种方式是

    AudioInputStream stream = AudioSystem.getAudioInputStream(new File("haha.mp3"));
    stream = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, stream);
    

    这种方式通过两句话就能把mp3转码成pcm,但是却报错缺少这样的转码器.所以还是按照上面说的那种方法来吧.
    AudioSystem.getAudioInputStream()函数有5种形式:从文件中读取,从InputStream中读取,从URL中读取,从AudioInputStream中读取并将结果转换成某种编码,从AudioInputStream中读取并将结果转换成某种AudioFormat.

    八.使用SourceDataLine进行循环播放

    前面说过Clip非常适合进行循环播放,实际上SourceDataLine也是可以很方便进行循环播放的.实现的关键在于AudioInputStream的mark(int readLimit)和reset()两个函数.mark+reset是一种机制,在java流体系中很常见,它是这样一种机制:比如当前位置为第3个字节处,一旦调用mark(100),就表示在3处做了一个标记,然后继续往前走,比如,假设走到66处,执行了reset(),于是一下子就回到了第3个字节处;如果在66处没有执行reset()而是继续往前走,走到110处(已经超过了100步限制),那么刚才在3处的mark标记就失效了.不是所有的InputStream子类都支持mark机制,可以通过调用InputStream#markSurported()函数来检测当前流是否支持mark机制.BufferedInputStream和ByteArrayInputStream是支持mark机制的.对于readLimit,并不是所有的流在走完readLimit步之后都会将标记置成无效,而是在走完readLimit步之后执行reset仍旧回到上一个mark处,简言之,就是实际的readLimit为max(显式readLimit,该流在内存中的最大空间).

    byte[]    abData = new byte[EXTERNAL_BUFFER_SIZE];
    int    nBytesRead = 0;
    int nPlayCount = 0;
    if (audioInputStream.getFrameLength() == AudioSystem.NOT_SPECIFIED ||
        audioFormat.getFrameSize() == AudioSystem.NOT_SPECIFIED)
    {
        out("cannot calculate length of AudioInputStream!");
        System.exit(1);
    }
    long lStreamLengthInBytes = audioInputStream.getFrameLength()
        * audioFormat.getFrameSize();
    if (lStreamLengthInBytes > Integer.MAX_VALUE)
    {
        out("length of AudioInputStream exceeds 2^31, cannot properly reset stream!");
        System.exit(1);
    }
    int nStreamLengthInBytes = (int) lStreamLengthInBytes;
    
    line.start();
    
    while (nPlayCount < PLAY_COUNT)
    {
        nPlayCount++;
        audioInputStream.mark(nStreamLengthInBytes);
        nBytesRead = 0;
        while (nBytesRead != -1)
        {
            try
            {
                nBytesRead = audioInputStream.read(abData, 0, abData.length);
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            if (nBytesRead >= 0)
            {
                int    nBytesWritten = line.write(abData, 0, nBytesRead);
            }
        }
        audioInputStream.reset();
    }
    line.drain();
    line.close();
    

    九.关于混音器mixer

    SourceDataLine和Clip是AudioSystem的输入端,TargetDataLine是AudioSystem的输出端.AudioSystem就像一个黑盒子,那么盒子里面装的是啥?实际上就是混音器.当同时播放两首乐曲时,需要混音器来将多个SourceDataLine和多个Clip的音频数据进行混合.
    查看系统中的全部混音器

        public static void listMixers() {
            Mixer.Info[] a = AudioSystem.getMixerInfo();
            for (int i = 0; i < a.length; i++) {
                System.out.println(a[i].getName());
            }
        }
    

    并不是所有的mixer都支持三种类型的DataLine,实际上,输入混音器和输出混音器是分开的.有的混音器支持SourceDataLine和Clip,有的混音器支持TargetDataLine,有的混音器什么也不支持.通过AudioSystem这个类来获取三种DataLine就避免了程序员手动查看哪些混音器可用于输入,哪些混音器可用于输出.

            Mixer.Info[] a = AudioSystem.getMixerInfo();
            for (int i = 0; i < a.length; i++) {
                Mixer mixer = AudioSystem.getMixer(a[i]);
                Line.Info[] b = {new Line.Info(SourceDataLine.class),
                        new Line.Info(TargetDataLine.class),
                        new Line.Info(Clip.class)};
                int ans = 0;
                for (int j = 0; j < b.length; j++) {
                    if (mixer.isLineSupported(b[j])) {
                        ans |= (1 << j);
                    }
                }
                System.out.println(a[i].getName() + " " + ans);
            }
    

    要想获得mixer,就要使用AudioSystem#getMixer(Mixer.Info)函数.要想获得DataLine,就要通过Mixer#getLine(Line.Info)函数.这个过程需要检测mixer支持DataLine的情况,这是挺费事的.幸好通过AudioSystem#getLine(Line.Info)函数可以直接获得想要的DataLine,这个函数把底层封装了一下.

    查看支持的文件类型

        public static void listSupportedTypes() {
            AudioFileFormat.Type[] aTypes = AudioSystem.getAudioFileTypes();
            for (int i = 0; i < aTypes.length; i++) {
                System.out.println(aTypes[i].getExtension());
            }
        }
    

    十.查看文件元数据

    查看文件元数据主要通过三个类:AudioFormat,AudioFileFormat,AudioInputStream.

    File file = new File("haha.wav");
    AudioFileFormat aff = AudioSystem.getAudioFileFormat(file);
    AudioInputStream ais = AudioSystem.getAudioInputStream(file);
    //AudioFormat既可以通过AudioInputStream获取,也可以通过AudioFileFormat获取.
    AudioFormat af = ais.getFormat();// aff.getFormat()
    out("---------AudioFileFormat---------");
    out("Type " + aff.getType());
    out("byteLength " + aff.getByteLength());
    out("frame length " + aff.getFrameLength());
    out("format " + aff.getFormat());
    out("properties " + aff.properties());
    out("--------AudioFormat----------");
    out("encoding " + af.getEncoding());
    out("channels " + af.getChannels());
    out("sampleRate " + af.getSampleRate());
    out("frameRate " + af.getFrameRate());
    out("properties " + af.properties());
    out("sampleSizeInBits " + af.getSampleSizeInBits());
    out("frameSize " + af.getFrameSize());
    out("--------------AudioInputStream-------");
    out("frameLength " + ais.getFrameLength());
    
    播放时长=frameLength/frameRate
    frameRate=sampleRate
    frameSize=channels*sampleSizeInBytes=channels*sampleSizeInBits/8
    AudioFileFormat.getByteLength=文件头长度+数据长度=文件头长度+frameSize*frameLength
    

    以上结论完全适用于wav文件,但不适用于mp3文件,因为mp3文件是经过压缩的,并不是原始音频数据.

    十一.音频文件类型转换

    AudioSystem.write(AudioInputStream,AudioFileFormat.Type,File)函数可以实现wav,aiff,au之间的转换.

    十二.播放音频的其他方法

    (1)使用Applet.getAudioClip(URL)来获取AudioClip

    import java.applet.Applet;
    import java.applet.AudioClip;
    import java.awt.GridLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.net.MalformedURLException;
    import java.net.URL;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import javax.swing.border.EmptyBorder;
    
    public class TestAudioClip extends JPanel {
    
        AudioClip audioClip;
    
        TestAudioClip(String source) throws MalformedURLException {
            super(new GridLayout(1, 0, 10, 10));
            setBorder(new EmptyBorder(20, 20, 20, 20));
            JButton play = new JButton("Play");
            play.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    audioClip.play();
                    audioClip.loop();
                }
            });
            add(play);
    
            JButton stop = new JButton("Stop");
            stop.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    audioClip.stop();
                }
            });
            add(stop);
    
            URL url = new URL(source);
            audioClip = Applet.newAudioClip(url);
            audioClip.play();
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Thread() {
                public void run() {
                    JFrame frame = new JFrame();
                    TestAudioClip pc = null;
                    try {
                        pc = new TestAudioClip(
                                "file:///C:/Users/weidiao/Documents/eclipseProject/实验室java/haha.mp3");
                    } catch (MalformedURLException e) {
                        e.printStackTrace();
                    }
                    frame.getContentPane().add(pc);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setVisible(true);
                }
            });
        }
    }
    

    (2)使用sun的AudioPlayer.player
    sun这个包不是java标准包,在eclipse中编写如下代码会报错:
    Access restriction: The type 'AudioPlayer' is not API (restriction on requir
    解决方法是:右键项目>属性>buildpath,把jdk这个库的access rule(权限规则)放宽些.

    import sun.audio.AudioPlayer;
    
    public class TestAudioPlayer {
        public static void main(String[] args) throws FileNotFoundException {
            AudioPlayer.player.start(new FileInputStream("haha.mp3"));
        }
    }
    

    十三.播放MIDI文件

    Sequencer sequencer = MidiSystem.getSequencer();
    sequencer.open();
    sequencer.setSequence(new FileInputStream("haha.mid"));
    sequencer.start();
    

    如果在classpath中添加了MP3SPI,播放很有可能没有声音.因为mp3spi.jar依赖的tritonus.jar会改变程序运行时行为.解决方法就是把mp3spi.jar和jlayer.jar和tritonus.jar移除掉.这个问题曾经令我困惑不已,为啥没有声音,我还以为我的扬声器坏了.结果把java文件在控制台下运行就能够发声了.
    运行完了之后会发现这个程序无法终止,始终可以在任务管理器中看到.这是因为Sequencer没有close.给Sequencer添加一个事件监听器,当播放结束时,关闭Sequencer.这样程序就可以正常终止了.同样的问题在sample那一家子里面也同样存在,可以通过添加事件监听器来监听播放结束事件.

    Sequencer sequencer = MidiSystem.getSequencer();
    sequencer.open();
    sequencer.setSequence(new FileInputStream("haha.mid"));
    sequencer.start();
    sequencer.addMetaEventListener(new MetaEventListener() {
        @Override
        public void meta(MetaMessage meta) {
        if (meta.getType() == 47) {
            sequencer.close();
        }
        }
    });
    

    十四.Midi系统概述

    java sound明显分为两大门派:sample和midi.sample直接描述音频数据波形,相当于直接告诉你应该做成什么样.midi描述的是音频指令,告诉终端应该发的音符,响度,持续时间等.MIDI把命令直接发送给终端,终端有很大的自主权决定怎样发声.
    sample系统和midi系统在Java API中设计的十分对称.熟悉了sample的那套API之后再看midi就有一种熟悉感.midi系统地入口类为MidiSystem,跟sample中的AudioSystem地位相当.通过MidiSystem可以管理MidiDivice,通过AudioSystem可以管理AudioDivice.通过MidiSystem可以获取Sequencer(序列器)和Synthesizer(合成器),序列器Sequencer用于播放一段声音,Synthesizer用于合成声音.
    Sequencer和Synthesizer都是接口,继承自MidiDivice接口,而MidiDivice继承自java.io.Closable.
    Transmitter和Receiver也都是接口,继承自java.io.Closable接口.Transmitter只有一个子接口MidiDiviceTransmitter,Receiver只有一个子接口MidiDiviceReceiver. Transmitter是MIDI输入端口,用于播放MIDI音频,Receiver是MIDI输出端口,用于录音.

    MidiEvent=MidiMessage(这是一个实体类)+持续时间tick.
    MidiMessage有三个子类:ShortMessage,MetaMessage,SysexMessage.
    MidiMessage有两个成员变量:int length和byte[] data. length表示数据长度,data表示数据.其中,data的第一个字节表示status.

    在java中,会看见很多byte使用int表示的,比如InputStream#read()读取一个字节,返回值为int.当读到末尾时,返回值为-1.之所以使用int来表示byte,是因为这里的byte是无符号byte,而java中有一个原则:一切数字皆有符号.于是有符号的byte无法表示128~255了,所以用int表示无符号byte.

    instruments乐器,programs,pathes,timbres意义相近,都是指某种音色,某种乐器.midi说的soundbank跟java中的soundbank不太一样,midi中的一个soundbank可以包含128种乐器,而java中的soundbank包含16383*128种乐器.也就是一个java soundbank包含16383个midi bank. 为了定位一个乐器,java sound使用了Patch来定位Instrument,Patch只有两个成员方法:getBank(),getProgram().

    十五.后记

    Java sound越学越感觉像一个无底洞,任何一门技术都是一个无底洞.世界上有那么多的无底洞,千万不能掉到一个无底洞里出不来,不能因为探索一个无底洞而忽略了其它无底洞.
    本文写于2016.9.12,用markdown重写于2016.11.20

  • 相关阅读:
    CSS深入之第四天
    CSS之第三天总结
    第二天对CSS的学习
    开始走进CSS世界
    Hbuilder实用技巧
    项目总结
    CSS3的chapter6
    CSS3的chapter5
    CSS3的chapter4
    CSS3的chapter3
  • 原文地址:https://www.cnblogs.com/weiyinfu/p/6081941.html
Copyright © 2011-2022 走看看