liang's profile阿斯提亚神殿——梦幻天子's skyPhotosBlogLists Tools Help
    April 15

    java多线程同步(转载)

    下面是我原来在CSDN论坛上看到的一个贴子,涉及到同步,wait(),notify()等概念的理解,我试着根据原来的一些回复和Think in Java上的相关概念将wait()和notify()这两个方法剖析了一下,欢迎指教.

    问题如下:

    file://分析这段程序,并解释一下,着重讲讲synchronized、wait(),notify 谢谢!
    class ThreadA
    {
      public static void main(String[] args)
      {
        ThreadB b=new ThreadB();
        b.start();
        System.out.println("b is start....");
        synchronized(b)//括号里的b是什么意思,起什么作用?
        {
          try
          {
     System.out.println("Waiting for b to complete...");
     b.wait();//这一句是什么意思,究竟让谁wait?
            System.out.println("Completed.Now back to main thread");
          }catch (InterruptedException e){}
        }
        System.out.println("Total is :"+b.total);
       }
    }


    class ThreadB extends Thread
    {
      int total;
      public void run()
      {
        synchronized(this)
        {
          System.out.println("ThreadB is running..");
          for (int i=0;i<100;i++ )
          {
            total +=i;
            System.out.println("total is "+total);
          }
          notify();
        }
      }
    }

    要分析这个程序,首先要理解notify()和wait(),为什么在前几天纪录线程的时候没有纪录这两个方法呢,因为这两个方法本来就不属于Thread类,而是属于最底层的object基础类的,也就是说不光是Thread,每个对象都有notify和wait的功能,为什么?因为他们是用来操纵锁的,而每个对象都有锁,锁是每个对象的基础,既然锁是基础的,那么操纵锁的方法当然也是最基础了.

     再往下看之前呢,首先最好复习一下Think in Java的14.3.1中第3部分内容:等待和通知,也就是wait()和notify了.

    按照Think in Java中的解释:"wait()允许我们将线程置入“睡眠”状态,同时又“积极”地等待条件发生改变.而且只有在一个notify()或notifyAll()发生变化的时候,线程才会被唤醒,并检查条件是否有变."

      我们来解释一下这句话.
      "wait()允许我们将线程置入“睡眠”状态",也就是说,wait也是让当前线程阻塞的,这一点和sleep或者suspend是相同的.那和sleep,suspend有什么区别呢?

       区别在于"(wait)同时又“积极”地等待条件发生改变",这一点很关键,sleep和suspend无法做到.因为我们有时候需要通过同步(synchronized)的帮助来防止线程之间的冲突,而一旦使用同步,就要锁定对象,也就是获取对象锁,其它要使用该对象锁的线程都只能排队等着,等到同步方法或者同步块里的程序全部运行完才有机会.在同步方法和同步块中,无论sleep()还是suspend()都不可能自己被调用的时候解除锁定,他们都霸占着正在使用的对象锁不放.
       而wait却可以,它可以让同步方法或者同步块暂时放弃对象锁,而将它暂时让给其它需要对象锁的人(这里应该是程序块,或线程)用,这意味着可在执行wait()期间调用线程对象中的其他同步方法!在其它情况下(sleep啊,suspend啊),这是不可能的.
       但是注意我前面说的,只是暂时放弃对象锁,暂时给其它线程使用,我wait所在的线程还是要把这个对象锁收回来的呀.wait什么?就是wait别人用完了还给我啊!
       好,那怎么把对象锁收回来呢?
       第一种方法,限定借出去的时间.在wait()中设置参数,比如wait(1000),以毫秒为单位,就表明我只借出去1秒中,一秒钟之后,我自动收回.
       第二种方法,让借出去的人通知我,他用完了,要还给我了.这时,我马上就收回来.哎,假如我设了1小时之后收回,别人只用了半小时就完了,那怎么办呢?靠!当然用完了就收回了,还管我设的是多长时间啊.

       那么别人怎么通知我呢?相信大家都可以想到了,notify(),这就是最后一句话"而且只有在一个notify()或notifyAll()发生变化的时候,线程才会被唤醒"的意思了.
       因此,我们可将一个wait()和notify()置入任何同步方法或同步块内部,无论在那个类里是否准备进行涉及线程的处理。而且实际上,我们也只能在同步方法或者同步块里面调用wait()和notify().

       这个时候我们来解释上面的程序,简直是易如反掌了.

       synchronized(b){...};的意思是定义一个同步块,使用b作为资源锁。b.wait();的意思是临时释放锁,并阻塞当前线程,好让其他使用同一把锁的线程有机会执行,在这里要用同一把锁的就是b线程本身.这个线程在执行到一定地方后用notify()通知wait的线程,锁已经用完,待notify()所在的同步块运行完之后,wait所在的线程就可以继续执行.

     

     

    java多线程设计wait/notify机制

    多线程之间需要协调工作。例如,浏览器的一个显示图片的线程displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示了”,这时,displayThread继续执行。

    以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。例如:

    synchronized(obj) {
        while(!condition) {
            obj.wait();
        }
        obj.doSomething();
    }

    当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。

    在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:

    synchronized(obj) {
        condition = true;
        obj.notify();
    }

    需要注意的概念是:

    # 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {...} 代码段内。

    # 调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {...} 代码段内唤醒A。

    # 当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。

    # 如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。

    # obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。

    # 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。

     

    synchronized的4种用法

    1.方法声明时使用,放在范围操作符(public等)之后,返回类型声明(void等)之前.即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入.
     
          例如:

          public synchronized void synMethod() {
            //方法体
          }

        2.对某一代码块使用,synchronized后跟括号,括号里是变量,这样,一次只有一个线程进入该代码块.例如:

          public int synMethod(int a1){
            synchronized(a1) {
              //一次只能有一个线程进入
            }
          }

        3.synchronized后面括号里是一对象,此时,线程获得的是对象锁.例如:

    public class MyThread implements Runnable {
      public static void main(String args[]) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt, "t1");
        Thread t2 = new Thread(mt, "t2");
        Thread t3 = new Thread(mt, "t3");
        Thread t4 = new Thread(mt, "t4");
        Thread t5 = new Thread(mt, "t5");
        Thread t6 = new Thread(mt, "t6");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
      }

      public void run() {
        synchronized (this) {
          System.out.println(Thread.currentThread().getName());
        }
      }
    }


     
        对于3,如果线程进入,则得到对象锁,那么别的线程在该类所有对象上的任何操作都不能进行.在对象级使用锁通常是一种比较粗糙的方法。为什么要将整个对象都上锁,而不允许其他线程短暂地使用对象中其他同步方法来访问共享资源?如果一个对象拥有多个资源,就不需要只为了让一个线程使用其中一部分资源,就将所有线程都锁在外面。由于每个对象都有锁,可以如下所示使用虚拟对象来上锁:

    class FineGrainLock {

       MyMemberClass x, y;
       Object xlock = new Object(), ylock = new Object();

       public void foo() {
          synchronized(xlock) {
             //access x here
          }

          //do something here - but don't use shared resources

          synchronized(ylock) {
             //access y here
          }
       }

       public void bar() {
          synchronized(this) {
             //access both x and y here
          }
          //do something here - but don't use shared resources
       }
    }

     

        4.synchronized后面括号里是类.例如:

    class ArrayWithLockOrder{
      private static long num_locks = 0;
      private long lock_order;
      private int[] arr;

      public ArrayWithLockOrder(int[] a)
      {
        arr = a;
        synchronized(ArrayWithLockOrder.class) {//-----------------------------------------这里
          num_locks++;             // 锁数加 1。
          lock_order = num_locks;  // 为此对象实例设置唯一的 lock_order。
        }
      }
      public long lockOrder()
      {
        return lock_order;
      }
      public int[] array()
      {
        return arr;
      }
    }

    class SomeClass implements Runnable
    {
      public int sumArrays(ArrayWithLockOrder a1,
                           ArrayWithLockOrder a2)
      {
        int value = 0;
        ArrayWithLockOrder first = a1;       // 保留数组引用的一个
        ArrayWithLockOrder last = a2;        // 本地副本。
        int size = a1.array().length;
        if (size == a2.array().length)
        {
          if (a1.lockOrder() > a2.lockOrder())  // 确定并设置对象的锁定
          {                                     // 顺序。
            first = a2;
            last = a1;
          }
          synchronized(first) {              // 按正确的顺序锁定对象。
            synchronized(last) {
              int[] arr1 = a1.array();
              int[] arr2 = a2.array();
              for (int i=0; i
                value += arr1[i] + arr2[i];
            }
          }
        }
        return value;
      }
      public void run() {
        //...
      }
    }

     

        对于4,如果线程进入,则线程在该类中所有操作不能进行,包括静态变量和静态方法,实际上,对于含有静态方法和静态变量的代码块的同步,我们通常用4来加锁.

    以上4种之间的关系:

        锁是和对象相关联的,每个对象有一把锁,为了执行synchronized语句,线程必须能够获得synchronized语句中表达式指定的对象的锁,一个对象只有一把锁,被一个线程获得之后它就不再拥有这把锁,线程在执行完synchronized语句后,将获得锁交还给对象。
        在方法前面加上synchronized修饰符即可以将一个方法声明为同步化方法。同步化方法在执行之前获得一个锁。如果这是一个类方法,那么获得的锁是和声明方法的类相关的Class类对象的锁。如果这是一个实例方法,那么此锁是this对象的锁。


      下面谈一谈一些常用的方法:

      wait(),wait(long),notify(),notifyAll()等方法是当前类的实例方法,
       
            wait()是使持有对象锁的线程释放锁;
            wait(long)是使持有对象锁的线程释放锁时间为long(毫秒)后,再次获得锁,wait()和wait(0)等价;
            notify()是唤醒一个正在等待该对象锁的线程,如果等待的线程不止一个,那么被唤醒的线程由jvm确定;
            notifyAll是唤醒所有正在等待该对象锁的线程.
            在这里我也重申一下,我们应该优先使用notifyAll()方法,因为唤醒所有线程比唤醒一个线程更容易让jvm找到最适合被唤醒的线程.

        对于上述方法,只有在当前线程中才能使用,否则报运行时错误java.lang.IllegalMonitorStateException: current thread not owner.


        下面,我谈一下synchronized和wait()、notify()等的关系:

    1.有synchronized的地方不一定有wait,notify

    2.有wait,notify的地方必有synchronized.这是因为wait和notify不是属于线程类,而是每一个对象都具有的方法,而且,这两个方法都和对象锁有关,有锁的地方,必有synchronized。

    另外,请注意一点:如果要把notify和wait方法放在一起用的话,必须先调用notify后调用wait,因为如果调用完wait,该线程就已经不是current thread了。如下例:

    /**
     * Title:        Jdeveloper's Java Projdect
     * Description:  n/a
     * Copyright:    Copyright (c) 2001
     * Company:      soho 
    http://www.ChinaJavaWorld.com
     * @author jdeveloper@21cn.com
     * @version 1.0
     */
    import java.lang.Runnable;
    import java.lang.Thread;

    public class DemoThread
        implements Runnable {

      public DemoThread() {
        TestThread testthread1 = new TestThread(this, "1");
        TestThread testthread2 = new TestThread(this, "2");

        testthread2.start();
        testthread1.start();

      }

      public static void main(String[] args) {
        DemoThread demoThread1 = new DemoThread();

      }

      public void run() {

        TestThread t = (TestThread) Thread.currentThread();
        try {
          if (!t.getName().equalsIgnoreCase("1")) {
            synchronized (this) {
              wait();
            }
          }
          while (true) {

            System.out.println("@time in thread" + t.getName() + "=" +
                               t.increaseTime());

            if (t.getTime() % 10 == 0) {
              synchronized (this) {
                System.out.println("****************************************");
                notify();
                if (t.getTime() == 100)
                  break;
                wait();
              }
            }
          }
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }

    }

    class TestThread
        extends Thread {
      private int time = 0;
      public TestThread(Runnable r, String name) {
        super(r, name);
      }

      public int getTime() {
        return time;
      }

      public int increaseTime() {
        return++time;
      }

    }

        下面我们用生产者/消费者这个例子来说明他们之间的关系:

        public class test {
      public static void main(String args[]) {
        Semaphore s = new Semaphore(1);
        Thread t1 = new Thread(s, "producer1");
        Thread t2 = new Thread(s, "producer2");
        Thread t3 = new Thread(s, "producer3");
        Thread t4 = new Thread(s, "consumer1");
        Thread t5 = new Thread(s, "consumer2");
        Thread t6 = new Thread(s, "consumer3");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
      }
    }

    class Semaphore
        implements Runnable {
      private int count;
      public Semaphore(int n) {
        this.count = n;
      }

      public synchronized void acquire() {
        while (count == 0) {
          try {
            wait();
          }
          catch (InterruptedException e) {
            //keep trying
          }
        }
        count--;
      }

      public synchronized void release() {
        while (count == 10) {
          try {
            wait();
          }
          catch (InterruptedException e) {
            //keep trying
          }
        }
        count++;
        notifyAll(); //alert a thread that's blocking on this semaphore
      }

      public void run() {
        while (true) {
          if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("consumer")) {
            acquire();
          }
          else if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("producer")) {
            release();
          }
          System.out.println(Thread.currentThread().getName() + " " + count);
        }
      }
    }

           生产者生产,消费者消费,一般没有冲突,但当库存为0时,消费者要消费是不行的,但当库存为上限(这里是10)时,生产者也不能生产.请好好研读上面的程序,你一定会比以前进步很多.

          上面的代码说明了synchronized和wait,notify没有绝对的关系,在synchronized声明的方法、代码块中,你完全可以不用wait,notify等方法,但是,如果当线程对某一资源存在某种争用的情况下,你必须适时得将线程放入等待或者唤醒.

    April 10

    [转贴]Java媒体架构(JMF)

     
    Java媒体架构(JMF)是一个令人激动的通用的API,它允许Java开发者用许多不同的方法处理媒体。本指南主要通过使用工作的例子提供一个JMF的一些主要的特征的概述。阅读完本指南后,你将会明白JMF体系结构中的主要播放功能。你同样能正确的使用JMF,使用现存的例子和可为更多特殊功能扩展的源代码。

    本指南包含着以下主题:
    · 下载和安装JMF
    · 主要的JMF类以及它们在JMF体系结构中的应用
    · 播放本地的媒体文件
    · 为媒体的存取和操作制作以和图形用户界面(GUI)
    · 通过网络传播媒体
    · 通过网络接收媒体

    几乎所有的媒体类型的操作和处理都可以通过JMF来实现。全面的讨论JMF所提供的所有特征已经超过了本指南的范围,我们将使用三个简单的媒体应用程序来学习此框架的构建模块。通过这个方法,本指南将为你未来学习和实施更多特殊的应用提供准备。
    我应该使用此指南吗?

    本指南会带你学习使用JMF工作的基础。为完成这些,我们会创建三个的独立工作的例程序。每个例子都会建立前一个例子的基础上,显示JMF功能性的不同方面。
    在本指南中的例子假定你曾经使用过并且已经熟悉了Java程序语言。除了Java核心和JMF的类之外,我们会使用一些Java AWT和Swing类(用于创建GUI),也会有一些Java网络类(用于在网络中传输媒体)。对GUI和网络类一些熟悉有助于你更快的明白观点和这里的例子,但并非是阅读本指南必须的。

    我们将学习的例程序如下
    · 一个简单的音频播放器(JMF的HelloWorld应用):这个字符界面的播放器通过在命令行中简单的输入媒体文件的名字就可以播放大多数的音频类型。此音频播放器的演示大体上显示了JMF的特有的类。
    · 一个图形界面的媒体播放器:我们将使用JMF内置的接口组件来建立图形界面,所以在此练习中必须有一些图形界面的编程经验。这个媒体阅览器演示使用了一些Java AWT和Swing类来为用户显示图形组件。
    · 一个媒体广播应用:此应用程序允许一个本地媒体文件通过网络传播。此程序能灵活的使媒体只传输到指定的网络节点,或者传输到一个子网络中的所有节点。此演示使用了一些Java的网络APIs来在网络中传输媒体。
    作为第三个练习的一部分,我们将修改图形界面的播放器,让其能接收并且播放媒体。
    跳至23页观看Resources,文章,指南,和其他参考书目的列表,这会帮助你学习到更到关于此指南包括的主题。

    安装需求
    要运行此指南中的例程序,你需要如下的工具和组件:
    ·  Java 2 平台,标准版,编译和运行演示程序
    ·  Java媒体框架,版本2.1.1a或者更高
    · 一块已经安装并且配置号的适当的声卡
    · 一台或者多台测试机器
    · 演示的源代码文件在mediaplayer.jar中
    最后的一个演示应用显示了JMF在网络中的应用。如果需要,此演示能运行在一个独立的机器上,使用此机器即是传输方也是接收方。可是要观察到在网络中使用JMF的所有功能,你仍然需要至少两台联网的机器。
    在23页中的Resources可下载Java 2平台,完整的源代码文件,以及其他一些完成本指南所需要的工具。

    下载安装文件
    将JMF安装到你的计算机中的第一步是在JMF的主页中下载安装文件,它同样包括了JMF源代码和API文档的链接。23页的Resources中有下载JMF的链接。
    目前,JMF有Windows, Solaris, Linux等版本,以及可运行在任何装有虚拟机的计算机上一个纯Java版本。为了增加性能,你需要下载一个与你操作系统所适应的版本。任何在一个操作系统JMF版本下书写和编译的代码都可以方便的移植到另外的操作系统上。例如,如果你下载了一个Solaris版本的JMF并且编译了一个类,这些类就可以在Linux上使用,不会有任何问题。
    作为选择,你可以选择下载纯Java版本,或者跨平台版本的JMF。这些版本没有使用操作系统特有的库文件。如果没有合适的JMF版本适合的操作系统,那么跨平台版本就是一个不错的选择。

    安装JMF
    下载完JMF安装程序后,双击安装程序的图标。
    大部分安装程序都会有个选项,安装本地库到系统目录中;例如,Windows版本安装程序会有一个选项“Move DLLs to Windows/System directory.”。最好将此选项选中,因为它能确保这些操作系统的库文件能正确的安装
    在安装的过程中,你还需要选择项目来更新系统的CLASSPATH和PATH变量。如果这些选项被关闭,那么在你编译和运行本指南的例程序的时候就需要在classpath中引入JMF的jar文件。

    关于作者
    Eric Olson在Retek Inc工作的软件工程师。它在Java平台上有四年的工作经验,并且在不同的基于Java的技术上富有经验,包括JMF, Jini, Jiro, JSP, servlets, and EJBs。Eric毕业于St. Paul, MN的St. Thomas大学,获得计算机科学的学位。他在IBM的SanFrancisco项目组工作,负责WebSphere商业组件。他同时再为Imation Corp.工作,负责存储应用。现在,他正在开发零售行业的基于web的软件解决方案。再业余的时间,Eric和Paul
    Monday在Stereo Beacon上合作—一个分布式的点对点的基于JMF的媒体播放器。联系
    Eric zpalffy@yahoo.com.

    第二节. 一个简单的音频播放器
    浏览
    在本节中,我们将进行创建一个简单的音频播放器的第一个练习。本例将介绍Manager类和Player接口,中两个都是建立大多数基于JMF应用的重要部分。
    本例的功能目标是在字符界面下播放本地的音频文件。我们将学习此源代码,并了解每一行所做的任务。完成本节后,你将会有一个基于JMF的可播放包括MP3, WAV, AU等多种音频文件的演示程序。
    在本练习后的源代码分类种可查询文件SimpleAudioPlayer.java。

    引入必要的类
    SimpleAudioPlayer类中包括了一些调用,在其前几行中需要引入所有必要的类:
    import javax.media.*;
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    import java.net.MalformedURLException;

    The javax.media包是由JMF定义的多个包之一。javax.media是一个核心包,包括了定义Manager类和Player接口等。本节中,我们主要学习Manager类和Player接口,其余的javax.media类放在后面的章节中。
    除了引入javax.media声明外,以上的代码片断引入了一些创建媒体播放器的输入的声明。

    Player接口
    在下面的代码片断中,创建一个公共类SimpleAudioPlayer并举例定义一个Player变量:
    public class SimpleAudioPlayer {
    private Player audioPlayer = null;

    术语Player听起来由点熟悉,因为它是建立在我们公用的音频或者视频播放器的基础上的。事实上,这个接口的例子就像是当作它们的真实的副本。 Players揭示了一个实体上的媒体播放器(如立体音箱系统或者VCR)涉及到功能上的方法。例如,一个JMF媒体播放器可以开始和结束一个媒体流。在本节种,我们将使用Player的开始和结束功能。

    在一个文件上创建一个Player
    使用JMF获得一个特定媒体文件的Player实例非常简单。Manager类在JMF中如同一个工厂制作许多的特殊接口类型,包括Player接口。因此,Manager类的责任就是创建Player实例,如下例:
    public SimpleAudioPlayer(URL url) throws IOException,
    NoPlayerException,
    CannotRealizeException {
    audioPlayer = Manager.createRealizedPlayer(url);
    }
    public SimpleAudioPlayer(File file) throws IOException,
    NoPlayerException,
    CannotRealizeException {
    this(file.toURL());
    }


    如果你看完本节的代码,你可以注意到Manager类包含了创建一个Player实例的其他方法。我们会研究其中的一些,如在后面的章节中的DataSource或者MediaLocator的实例化。

    Player的状态
    JMF定义了大量的一个Player实例可能存在的不同状态。如下:
    · Prefetched
    · Prefetching
    · Realized
    · Realizing
    · Started
    · Unrealized

    使用这些状态
    因为使用媒体常常是资源非常密集的,由JMF对象揭示的许多方法都是不闭塞的,允许一系列事件监听的状态改变的异步通知。例如,一个Player在它可以启动之前,必须经过Prefetched和Realized状态。由于这些状态的改变都需要一些时间来完成,JMF媒体应用可以分配一个线程来初始化创建 Player实例,然后再继续其他的操作。当Player准备就绪的时候,它会通知应用程序其状态已经改变。

    在一个如同我们的这样简单的程序中,多功能性的类型并不是很重要。处于这个原因,Manager类也提供了一些创建Realized player的有用方法。调用一个createRealizedPlayer()方法来阻塞调用线程,直到player达到Realized状态。为了调用一个无阻塞的创建player的方法,我们在Manager类中使用了一个createPlayer()方法。下面的一行代码中创建了一个我们需要在例程序中使用的
    Realized player:
    audioPlayer = Manager.createRealizedPlayer(url);


    启动和停止Player
    设定一个Player实例的启动或是停止就如同调用Player的一个简单的认证方法,如下所示:
    public void play() {
    audioPlayer.start();
    }
    public void stop() {
    audioPlayer.stop();
    audioPlayer.close();
    }

    调用SimpleAudioPlayer类中的play()方法来实现调用Player实例的start()方法。调用此方法后,你能听到本地的喇叭的声音文件。同样的,stop()方法使player停止并且关闭掉Player对象。

    对于读取和或者播放本地媒体文件来说,关闭Player实例释放所有资源是一个有用的方法。因为这是一个简单的例子,关闭Player是终止一个会话可接受的方法。但是在实际的应用中,你需要小心的确认在除掉Player之前必须要关闭掉。一但你已经关闭掉player,在再次播放一个媒体之前你必须要创建一个新的Player实例(等待它的状态改变)。

    建立一个SimpleAudioPlayer

    最后,这个媒体播放应用程序要包含一个可以从命令提示行中输入命令而调用的main()方法。在此main()方法中,我们将调用创建SimpleAudioPlayer的方法:
    File audioFile = new File(args[0]);
    SimpleAudioPlayer player = new SimpleAudioPlayer(audioFile);


    在播放音频文件之前的唯一的一些事情就是调用已经创建的音频player的方法play(),如下所示:

    player.play();


    要停止和清除掉音频player,在main()方法中也应该有如下调用:
    player.stop();


    编译和运行SimpleAudioPlayer
    通过在命令提示行输入javac SimpleAudioPlayer.java来编译例程序。所创建的文件SimpleAudioPlayer.class在当前工作目录中。
    然后在命令提示行中键入如下命令来运行例程序:
    java SimpleAudioPlayer audioFile

    将audioFile替换成你本地机器上的音频文件。所有的相对文件名都试相对于当前的工作目录。你会看到一些当前正在播放文件的标志信息。要终止播放,按下回车键。
    如果编译失败,确认JMF的jar文件已经正确的包含在CLASSPATH环境变量中。


    第三节. JMF用户界面组件
    播放视频
    在前一节中,我们学习了建立一个通过字符界面播放音频文件的应用程序。JMF中一个最重要的特点就是你不需要为了配置媒体播放器而去了解媒体文件的格式;一切都内置了。举一个例子,再我们前面的例子中,需要使用MP3格式的时候,我们不需要让应用程序为一个MP3文件建立一个特殊的Player。
    如同你将会再本节所见到的,对于视频文件的操作同样有效。JMF有所有媒体文件类型接口的详细资料。
    处理视频媒体与音频最大的不同就是,我们必须建立一个能播放视频的显示屏幕。幸运的是,JMF能处理许多的这些资料。如同再上例一样我们会建立一个Player对象,并且使用很多的可视组件来直接从JMF对象中创建我们的可视的媒体浏览器。
    本节中,我们将学习两个例程序。In this section, we&aposll walk through the second example application. 请再后面的练习的源代码分布中查阅MediaPlayerFrame.java。

    关于例子
    在本节中,我们将创建一个能显示和运行本地音频和视频媒体的应用程序。作为练习的一部分,我们将研究JMF内置的一些GUI组件。熟悉AWT和Swing将有助于你理解本例,但这并不是必须的。除非需要直接涉及到JMF的GUI组件,或者我们是不会详细介绍源代码的。你可以在源代码的注释中找到这里未涉及的详细说明。
    本例中我们使用的许多概念,类和方法都和第一个例子的类似。建立Player的基本操作大都一样。最大的不同就是我们需要对Player对象专研更深一点,特别当需要从Player获取媒体信息的时候。

    如何开始
    视频播放器例子被设计得如同音频播放例子一样通过命令行来运行,但是本例需要建立在GUI基础上。如同在上节一样,我们先通过媒体文件名调用应用。然后,应用程序显示一个带有可操作媒体组件的窗体。
    在MediaPlayerFrame开始的一行中我们定义了类并扩展自,javax.swing.Jframe类。这就是使媒体播放器如同一个在桌面上的单独窗体的方法。任何客户机程序创建了本媒体播放对象后都可以通过调用Jframe类中定义的show()方法来显示。
    下面是一个MediaPlayerFrame正在播放MPEG电影的屏幕截图:

    获取GUI组件
    Player界面有一些方法来获取已选择可视组件的涉及。在MediaPlayerFrame中,我们使用如下组件:
    · player.getVisualComponent()是一个播放所有视频媒体的可视组件。
    · player.getControlPanelComponent() 是一个操作时间轴的可视组件(包括开始,停止,回放),也包含了一些媒体流的有用信息。
    · player.getGainControl().getControlComponent() 是操作音量(增加)的可视组件。getGainControl()方法返回一个GainControl实例,可用于改变节目的增加等级。

    使用可视化组件
    上面的界面方法都返回一个java.awt.Component类的实例。没个实例都视可加载到我们窗体上的可视组件。这些组件都与Player有直接的联系,所以在这些组件上的所有可视元素的处理都会产生Player播放媒体后相应的变化。
    在我们将这些组件加入到我们的窗体的之前,必须要保证它们不为空。因为并不是所有的媒体播放器包括每一种可视组件,我们只需添加相关播放器类型的组件。比如,一般来说一个音频播放器没有可视组件,所以getVisualComponent()就要返回空。你不会想在音频播放器窗体上添加可视组件的。

    获得媒体的特殊控制
    一个Player实例也可以通过getControl()和getControls()方法来暴露其控制,getControls()返回一个控制对象集,而getControl()返回一个控制。不同的播放器类型可选择为特殊的操作来暴露控制集去指定的媒体类型,或者用于获取该媒体的传输机制。如果你在写一个只支持某些媒体类型的播放器,你需要依靠某些在Player实例中可用Control对象。
    由于我们的播放器是非常抽象的,被设计于播放多种不同媒体类型,我们简单的为用户暴露所有的Control对象。如果找到任何扩展的控制集,我们就可使用getControlComponent()方法来增加相应的可视控件到标签面板上。通过这个办法,用户就可以观察播放器上的所有组件。以下代码片断将所有的控制对象暴露给用户:
    Control[] controls = player.getControls();
    for (int i = 0; i< controls.length; i++) {
    if (controls[i].getControlComponent() != null) {
    tabPane.add(controls[i].getControlComponent());
    }
    }

    为了使一个真实的应用程序能用Control实例做一些有用的事(除了能显示可视组件之外),应用程序需要知道该Control的特殊类型,并分配它。此后,应用程序就可使用这些control来控制媒体节目了。例如,如果你知道你经常使用的媒体暴露 javax.media.control.QualityControl类型的Control,你能使用QualityControl界面,之后在 QualityControl界面上通过调用各种方法来改变性质设定。

    使用一个MediaLocator
    在我们新的基于GUI的媒体播放器和我们的第一个简单播放器之间最大的不同就是,我们使用一个MediaLocator对象而不是URL来创建Player实例,如下所示:
    public void setMediaLocator(MediaLocator locator) throws IOException,
    NoPlayerException, CannotRealizeException {
    setPlayer(Manager.createRealizedPlayer(locator));
    }

    我们将在稍后的章节中讨论这个变化的原因。目前,在网络上资源站点上,关于MediaLocator对象和URL的描述被认为是非常相似的。事实上,你可以从一个URL创建一个MediaLocator,也可以从MediaLocator获取到URL。我们的新媒体播放器一个URL中创建一个 MediaLocator,并使用该MediaLocator通过文件创建了一个Player。

    编译和运行MediaPlayerFrame
    通过在命令提示行输入javac MediaPlayerFrame.java来编译例程序。在工作目录下将创建一个名为MediaPlayerFrame.class的文件。
    在命令提示行中键入如下来运行例程序:
    java MediaPlayerFrame mediaFile

    你需要用你本机上的一个媒体文件来替换掉mediaFile(音频或者视频文件都可以)。所有的相对文件名都是相对于当前工作目录。你会看见一个显示控制媒体文件的GUI控制集的窗口。欲了解JMF支持的音频和视频文件列表,在23页的资源。
    如果初始编译时失败,请确认JMF的jar文件已经包含在当前的CLASSPATH环境变量中。

    MediaPlayerFrame在行动
    在本节前你看见的一个视频播放器正在播放MPEG视频文件的屏幕截图。下面的屏幕截图显示了一个音频播放器正在播放一个MP3文件:
    要更多的学习本练习中的例子,查看完成的MediaPlayerFrame源代码。

    第四节. JMF概念
    JMF体系结构
    你曾见过了使用JMF播放本地媒体文件是多么的容易,现在我们将后退一步,来看看一幅是如何通过JMF创建了如此成熟的基于媒体的应用程序的大的画面,是如何通过JMF创建了如此成熟的基于媒体的应用程序。全面的了解JMF体系结构是没有意义的,本节将给你一个大体的概念,关于高级的JMF组件是如何组合起来创建想得到的东西。
    JMF的组件结构非常的灵活,它的组件一般可以分成三个部分:
    · Input描述某种被用于在进程休息的时候作为一个输入的媒体。
    · process执行某些输入上的活动。一个过程有一个明确的输入和输出。大量的过程可用, 能被用于一个输入或者一批输入。这些过程能被联系起来,一个过程的输出被用于另外一个过程的输入。在这种风格中,大量的过程可能被应用于一个输入。(这段期间是可选择的——我们开始的两个例子没有包含真正的数据过程,只有一个来自文件的输入和一个通过Player的输出。)
    · Output 描述了媒体的某些目的地。

    从这些描述中,你可以想象到JMF组件体系结构听起来就好像在一个典型的立体声系统或者VCR之后。很容易设想到,使用JMF就如同打开电视或者在立体声音箱系统下调节声音的风格。例如,录制喜爱的电视节目的简单的动作能在这些组件的基础中:
    · Input 是电视广播流,在同一个频道运输音频和视频。
    · Process 是一个记录设备(就是,一个VCR或者许多的数字设备)转换模拟或者数字音频视频广播流成适合复制到磁带或其他媒体上的格式。
    · Output 是记录已格式化轨迹(音频和视频)到某些类型的媒体上。

    JMF资料处理模式
    以下图片说明了JMF数据处理模块并对每个类型给出了例子:
    使用此模式,很容易明白我们前面的两个例子,从文件中输入音频和视频并输出到本地计算机上。在后面的章节中,我们也会谈论一些通过传播和接收音频媒体的JMF网络功能。

    处理模型例子
    将JMF 的输入,处理和输出模式联系起来,我们能开始想象许多基于媒体的操作都可能通过JMF完成。一个例子,转换一种媒体类型为其他类型并将其输出存储到一个新的文件。举一个例子,我们想要在不损坏原始文件的前提下转化一个WAV格式的音频文件为MP3格式。以下的过程模式插图,就是我们将开始执行转换的步骤:
    本例的输入是一个WAV文件。它被一个媒体格式转换工具加工,并输出到一个新的文件。现在,让我们看看JMF API中的这个模式的每一步。我们使用输入,处理和输出模式作为概念上的路标。

    JMF输入
    再JMF中,一般由一个MediaLocator对象来描述一个输入。如先前规定的,
    MediaLocator的外观和行为都非常象一个URL,这样它可以唯一确定网络上的一个资源。事实上,使用一个URL来创建一个MediaLocator是完全可能的;我们在前面的两个例子中就是这样做的。
    为了我们的媒体转换例子,我们需要建立一个MediaLocator来描述最初的WAV文件。如同我们将在后面的章节中见到的,一个 MediaLocator也可以用于描述一个跨越网络中媒体流。在这个案例中,MediaLocator会描述传播的URL――很像一个被URL指定的在 Web上的资源,用于取代指定一个本地文件系统的文件来建立MediaLocator。

    一个MediaLocator和一个URL之间的不同
    要成功的建立一个URL对象,需要适当的java.net.URLStreamHandler安装于系统中。这个流处理的用途是能够处理被URL描述的流类型。一个MediaLocator对象并没有这个需要。例如,我们的下个应用程序将使用实时传输协议(RTP)在网络上传输音频。由于多数的系统都未为 RTP协议安装一个URLStreamHandler,所以创建一个URL对象会失败。在这个应用中,只有MediaLocator对象会成功。
    要理解更多关于URL对象以及创建和注册一个URLStreamHandler的信息,查阅JDK帮助文档(查看23页资源)。

    JMF处理机
    当我们使用JMF的时候,应用程序的处理机组件被Processor接口实例描述。你需要已有些熟悉Processor,它扩展至Player接口。由于 Processor继承直Player接口,它同样也从Player继承所有可用属性。另外,Processor增加了两个属性:Configuring 和Configured。这些扩展的属性(和与之关联的方法)用于Processor从输入流收集信息时的通信。
    在我们的最后的例程序中,我们将建立一个Processor用于将MP3编码格式的音频转换成适合在网络上传播的格式。在稍后的板块中我们会讨论创建一个简单的Processor的步骤。

    JMF输出
    有少许的方法用于描述JMF中处理模式的输出状态。最简单的(并且我们将在最后一个例子中使用的)是javax.media.DataSink接口。一个 DataSink读取媒体内容并且将其传送到一些目的地。本节中最开始的音频格式转换过程中,MP3(输出)文件将被DataSink描述。在我们最后一个例子中,我们将使用一个DataSink在实际上完成网络中传播音频媒体的工作。一个DataSink是在Manager类中,由指定一个 DataSource(输入到DataSink)和一个MediaLocator(输出到DataSink)完成的。
    一个DataSource实例描述可用于Players,Processors和DataSinks的输入数据。一个处理机的输出也被描述成一个DataSource对象。
    这就是为什么处理器能彼此联系起来,在同一媒体数据中完成多种操作。这也是来自Processor的输出能作为输入被Player或者DataSink使用的原因(它可将媒体传递到输出目的地)。
    一个DataSink的最后目的文件由一个MediaLocator对象说明。如同前面一样,MediaLocator描述一个网络资源;这就是媒体流将被传递的地方。

    第五节.传播接收媒体
    JMF和实时传输协议(RTP)
    许多的友善网络的特征直接建立在JMF中,这些使为客户端程序通过网络传输和接收媒体非常容易。当在一个网络上的一个用户想要接收任何种类的媒体流的时候,它不需要在观看媒体前等待全部的广播下载到机器上;用户可以实时的观看广播。在流媒体中些提出了这个概念。通过流媒体,一个网络客户端能接收到其他机器上广播的音频,甚至获取正在发生的实况视频广播。
    在IETF RFC 1889中定义了实时传输协议(RTP)。发展在快速和可靠的状态下通过网络传输时间极其敏感的数据,RTP在JMF中用于提供给用户向其他网络节点中传输媒体流的方法。
    在本节中,我们将学习我们的最后一个例程序。这里,你将学习到如何传输一个存储在一台机器上的MP3文件到另外的在同一个网络的机器上去。实际的MP3源文件并不从主计算机上移除,它也不使复制到其他机器上去;事实上它将会转换成能使用RTP传输的文件格式并通过网络发送。一旦被一个客户端接收到,源文件(现在是RTP信息包的形式)可以再次传输,这一次是在接收机器上可播放的一种格式。
    在MediaTransmitter.java文件中源代码查看学习以下练习。

    设置处理模式
    我们可以在前面的章节中定义的处理模式的基础下来讨论我们的最终的例子。在传输机器上,处理模式看起来像这样:
    事实上,MediaTransmitter对象源代码包括了以下三行:
    private MediaLocator mediaLocator = null;
    private DataSink dataSink = null;
    private Processor mediaProcessor = null;


    这三个实例变量可以直接映射到前面的处理模式图表,如下:
    ·  mediaProcessor变量是我们的处理器;它将负责转换音频文件从MP3文件模式到一个适合通过RTP协议传输的格式。
    ·  dataSink变量是我们的输出块。
    · 当我们建立DataSink时我们需要指定一个MediaLocator,它是DataSink的目的文件。

    当我们通过运行DataSink我们的处理过的媒体,它将传输到我们在MediaLocator中指定的地点。

    RTP MediaLocator
    在前面的两个练习中,我们通过从文件中获得的一个URL建立了MediaLocator实例。在本练习中,我们必须建立一个MediaLocator来描述网络上媒体传播输出流;换句话说,我们必须创建一个能我们的音频传播的目的地的 MediaLocator。一个RTP MediaLocator符合如下规则,看起来就像一个典型的URL:
    rtp://address:port/content-type


    让我们看看上面URL规范的每一段:
    · address 是将传输的媒体的地址。以单播的模式传输(一个专用IP地址),地址将会是有意接收的机器的IP地址。以广播的模式传播(到子网中的所有机器),地址将会是以255作为最后的一块的子网地址。举个例子,如果我再子网中可指定地址为192.168.1和想要传播到子网中的所有节点,我可以指定 192.168.1.255作为地址;这样允许子网中的每个节点监听广播媒体。
    · port 必须是被传输者和接收者都允许的一个端口。
    · content-type 是媒体流类型。在我们的案子中,这个将会是音频。
    下面的一个简单的RTP传播MediaLocator例子会让所有在指定网络中的机器接收到媒体流:
    rtp://192.168.1.255:49150/audio


    创建一个处理机
    在setDataSource()方法中我们首先要做的就是创建一个Processor实例。
    下面的Processor的职责是转换MP3音频媒体为一个RTP来表示:
    public void setDataSource(DataSource ds) throws IOException,
    NoProcessorException, CannotRealizeException, NoDataSinkException {
    mediaProcessor = Manager.createRealizedProcessor(
    new ProcessorModel(ds, FORMATS, CONTENT_DESCRIPTOR));
    在Manager类中,我们能创建一个Processor对象,通过两种方法中的一种:
    createProcessor ()或者createRealizedProcessor()。你很可能会注意到这两个方法样式的显示和前面例子中创建一个Player的方法很相似。在目前的例子中,我们将创建一个已实现的Processor。我们这样做是因为我们使用的应用非常简单,在Processo处于Realized状态时我们不需要关心任何真实的工作。

    创建一个ProcessorModel
    创建一个已实现的Processor,我们需要创建一个为Processor描述输入和输出媒体类型的ProcessorModel实例。为了创建ProcessorModel,我们需要下面的一些:
    · 一个DataSource,将被处理的媒体(输入文件)。
    · 一个javax.media.Format数组,描述输入媒体的格式。
    · 一个javax.media.protocol.ContentDescriptor实例,为我们的处理机描述输出格式。传送者的DataSource是通过一个参数传递到此方法。

    定义输入和输出格式
    因为我们的MediaTransmitter类会被时常用于将输入媒体格式(MP3)转换成一种输出格式(音频RTP),中学对象被定义成静态。我们创建一个新的javax.media.format.AudioFormat实例用于描述媒体输入类型(在java帮助文档中查看可用格式)。这就是我们的处理机可以获取MP3音频文件的原因。
    我们也创建一个javax.media.protocol.ContentDescriptor实例来描述想要处理机输出的。在我们的案子中,这是一个RTP媒体流。

    这就是为什么我们的处理机可以只制造RTP流。
    下面的代码片断显示了我们如何设置格式和内容描述符变量,用于创建ProcessorModel对象:
    private static final Format[] FORMATS = new Format[] {
    new AudioFormat(AudioFormat.MPEG_RTP)};
    private static final ContentDescriptor CONTENT_DESCRIPTOR =
    new ContentDescriptor(ContentDescriptor.RAW_RTP);


    连接输入,处理机和输出
    现在我们有一个处于Realized状态的Processor,我们需要设置DataSink以能实际上传播RTP媒体。创建DataSink是简单的大概使用另外一个调用给Manager对象,如下所示:

    dataSink = Manager.createDataSink(mediaProcessor.getDataOutput(),
    mediaLocator);


    createDataSink ()方法获取新Processor的输出(作为一个DataSource参数)和MediaLocator对象,我们和MediaTransmitter 对象同时建立的。通过这样,你能开始我们的不同的组件是如何在处理模式中联系起来的:我们从一个Processor中获取输出并使用他们作为输入到其他组件。在这个特殊的应用中,Processor输出用于传输媒体的DataSink的一个输入。

    创建一个DataSource实例
    在这点上,我们全部都是做和设置我们的媒体播放器的广播传输。
    我们需要创建DataSource对象,我们用于创建处理机(就是,在我们的MediaTransmitter中,参数传递到setDataSource()方法)。下面是创建一个DataSource实例的代码:
    File mediaFile = new File(args[1]);
    DataSource source = Manager.createDataSource(new MediaLocator(
    mediaFile.toURL()));


    这段代码是在MediaTransmitter对象中的vmain()方法。这里我们通过从命令行输入的第二个参数创建一个File对象。我们通过文件创建一个MediaLocator,而后通过位置创建一个DataSource。这个新近创建的DataSource是一个涉及到传送者的输入文件。我们能使用这个DataSource初始化传输者。

    开始和停止MediaTransmitter
    我们通过调用其中的startTransmitting()方法来开始MediaTransmitter,如下所示:
    public void startTransmitting() throws IOException {
    mediaProcessor.start();
    dataSink.open();
    dataSink.start();
    }


    这个方法首先开启处理机,然后打开并启动DataSink。在这个调用后,接收机器就可在媒体传送者上监听。
    停止传输者是非常简单的。以下代码将DataSink和Processor都停止和关闭掉:
    public void stopTransmitting() throws IOException {
    dataSink.stop();
    dataSink.close();
    mediaProcessor.stop();
    mediaProcessor.close();
    }


    编译和运行MediaTransmitter
    通过在命令行中输入javac MediaTransmitter.java来编译例程序,可在你的工作目录中生成一个同名的.class文件。
    要运行例程序,在命令提示行中输入以下代码:
    java MediaTransmitter rtpMediaLocator audioFile


    此例将创建一个myAudio.mp3文件的媒体广播。不要忘记将rtpMediaLocator替换成一个媒体传输的RTP URL,如同先前讨论的。
    你同样也需要将audioFile替换成你本机的音频文件名。
    所有的相对文件名都是相对于当前工作目录的。你会看见一些信息标志正在播放的文件。按下Enter键来停止播放。

    为传送者的一个例命令行交互如下:
    java MediaTransmitter rtp://192.168.1.255:49150/audio myAudio.mp3
    如果初始编辑失败,确定JMF的jar文件包含CLASSPATH环境变量中。要近一步探索本程序和练习,请查阅MediaTransmitter源代码。

    接收传输的媒体
    现在你可能会问,“如果没有人可以看或者收听的话,这个传播媒体有什么好的?”
    幸运的是,设定一个接收传播媒体的客户端只需要对我们在第二个例程序的MediaPlayerFrame源代码做很小的改动。
    MediaPlayerFrame类需要一个很小的调节来接收和播放音频文件。在main()方法中,你需要注释掉如下的一行:
    mpf.setMediaLocator(new MediaLocator(new File(args[0]).toURL()));

    并且输入如下的一行:
    mpf.setMediaLocator(new MediaLocator(args[0]));

    这个简单的改动允许我们通过String来创建一个MediaLocator对象,而不是通过创建一个File来创建MediaLocator。
    其他代码都一样。

    指定RTP URL
    在12页的说明编译和运行MediaPlayerFrame介绍了如何编译和运行MediaPlayerFrame例程序。这唯一的不同就是你需要为传输者指定RTP URL。为接收者的例命令行交互如下:
    java MediaPlayerFrame rtp://192.168.1.255:49150/audio


    运行网络媒体传送者的注意事项
    如果你在网络上只有权使用一台机器,你仍然可以运行传输程序。当你启动传送程序的时候,你可以即使用RTP URL传输地址,也可指定你工作的机器的机器地址。为了能够调节传输,在开始前接收者必须使用精确的同样的RTP URL。
    如果你运行本例真实的网络版本,每台你使用的机器都需要安装JMF,不论是传输还是接收媒体流。这是必须的,因为不论是传送程序还是接收程序都大量的使用了JMF的API。
    在任一个案子中,确认在指定的RTP URL中使用了相同的地址和端口;否则媒体传输是不会工作的。

    第六节. 约束和资源
    摘要
    我希望本指南能给你提供如何使用JMF的API的有用的浏览。
    我们建立了三个小的应用程序来播放本地的音频和视频,也通过网络传播和接收媒体。这些应用程序的源代码中包含了很多的javadoc样式的注释。这就有助于你理解你剩余的问题。
    许多JMF的主题在本指南中并没有涉及。实际上,我们更关注JMF的基本概念和应用;在此基础上,我们能轻易地扩展学习的其他范围。要更深入JMF的应用程序,你可能想要学习下面的面板中所提到的主题。更近一步的阅读本指南中的主题,查阅23页的资源。

    高级主题
    大量的值得做的练习在本指南的范围之上。在简单的说明之下自己更进一步的学习,你可以扩展我们的应用程序代码,也可以反展你的JMF相关知识。使用以下的练习开始:
    · 媒体捕获:JMF包含了丰富的API来捕获媒体数据。如果你对使用JMF捕获媒体感兴趣,你可以使用 javax.media.CaptureDeviceManager类和javax.media.protocol.CaptureDevice接口的 API来学习。对于一个高级的练习,考虑使用CaptureDeviceManager和CaptureDevice接口来增加媒体捕获功能到媒体播放应用程序的GUI版本上。
    · 会话管理:由于本指南是一个JMF的说明,我们使输出表现非常的简单,仅仅实现了javax.media.DataSink输出。
    另外的输出表示是使用javax.media.rtp.SessionManager。这个管理类允许客户端创建并监视他们的RTP流和连接。通过 SessionManager并随后创建流,它可能非常的接近监视RTP会话。作为一个高级的练习,转换我们的地三个演示程序来使用 SessionManager,然后监听流出的RTP流已经哪些客户端在收听。
    · 使用JMF的多点传送:我们的广播演示应用程序说明了如何传送一个网络的媒体到另外一个网络的一或多台机器上去。它也可能使用JMF中的多点传输协议来提供给更复杂,多用户的网络。
    JMF用户指南提供了一个使用JMF的多播协议的更深入的探讨。更进一步追踪本主题查看23页资源。
    · 传输视频: 我们的最后一个演示应用程序着眼于如何传输一个MP3音频文件,但是JMF也能够通过网络传递视频。关注API文档中的Format和ContentDescriptor类获得如何使用的更好的方法。
    · 导入/导出RTP媒体流: JMF同样允许将RTP流保存为文件以便将来使用。举一个实例,一个远程电信会议可以保存下来以后再看。
    由于流已经保存再RTP格式中,已经不需要再次转换,这样可导致传输程序的性能改进。通过一个文件而不是URL来设置DataSink对象中输入/输出MediaLocator。你会再JMF用户指南中发现更深层次的主题探讨。

    资源
    JMF
    · 下载mediaplayer.jar,本指南中使用的完整的例源代码。
    ·  JMF主页 (http://www.javasoft.com/jmf)是最好的探讨JMF更多信息的资源。
    · 你可以找到JMF说明书(http://java.sun.com/products/java- media/jmf/2.1.1/specdownload.html),再Java开发者联盟上包括API文档和JMF用户指南。你必须有权使用所有的这些资源,如果你想做任何更深入的JMF编程的话。
    · 官方的JMF支持文件格式 页面
    (http://java.sun.com/products/java-media/jmf/2.1.1/formats.html) 列出了所有可为JMF辨识并播放的文件格式。此文件格式页面也包括了学习更多关于捕获设备和RTP格式的参考。
    · MPEG-4 Video for JMF (http://www.alphaworks.ibm.com/tech/mpeg-4), 来自IBM
    alphaWorks, 是一个JMF的视频编解码器。
    RTP
    ·  IETF RTP RFC (http://www.ietf.org/rfc/rfc1889.txt) 非常详细的描述了RTP协议。
    · 查看 JMF API Guide
    (http://java.sun.com/products/java-media/jmf/2.1.1/specdownload.html) ,有许多有关于RTP协议和描述以及它是如何在JMF上应用的。
    · 哥伦比亚大学有一个比较有用的RTP FAQ(http://www.cs.columbia.edu/~hgs/rtp/faq.html).
    Java技术
    ·  Java 2 Platform, Standard Edition (http://java.sun.com/j2se/) 可从sun公司获得。
    · sun的指南关于JFC/Swing (http://java.sun.com/docs/books/tutorial/uiswing/index.html)
    和 AWT (http://java.sun.com/docs/books/tutorial/information/download.html#OLDui) 是非常好的能学习到很多关于Java程序语言中GUI编程的好地方。
    · 另外一个sun指南学习network programming 基础
    (http://java.sun.com/docs/books/tutorial/networking/index.html)。
    多点传输协议
    · Explicit Multicast (XCAST)
    (http://oss.software.ibm.com/developerworks/opensource/xcast/) 是IP多点传输的一种形式,为非常多的多点传输组设计提供可升级的支持,这些组有些少量的参与者代表。XCAST 代码得到了IBM Common Public License的认可。
    · Todd Montgomery 的 MTP page (http://www.nard.net/~tmont/rm-links.html),
    在这里你能找到一个广泛的涉及到多点传输协议的列表。
    附加资源
    · 你可以在
    developerWorks Java technology zone (http://www-106.ibm.com/developerworks/java/)中找到许多的关于Java各方面的内容。
    · 查看 developerWorks tutorials page
    (http://www-105.ibm.com/developerworks/education.nsf/dw/java-onlinecourse-bytitle?OpenDocument&Count=for a complete listing of free tutorials.