DataSource의 복제를 통한 다중 플레이어 생성방법


안녕하세요. 지난번 강좌까지 실험 잘 하셨나요?
지난 번 까지의 소스만으로도 간단한 영상 음성 기록 프로그램을 만들수도 있고, 캡쳐 카드가 있다면, 비디오 카메라의 영상 출력을 캡쳐 카드의 입력단으로 연결하고, 프로그램을 약간 수정한다면, 컴퓨터로 비디오 카메라의 영상을 저장하는 프로그램을 만드는 것도 그리 어렵지 않다고 생각됩니다. 자. 한번 해보세요. 그리고 소스를 공개할 용의가 있는 분들은 함께 JMF를 시작하는 다른 분들에게도 정보를 나누어 주세요..어차피 기술은 공유되어야 더욱 더 발전됩니다. 혼자만 알고 있는 기술. 과연 그것이 자신만의 독보적인 핵심기술일까요.. 쩝.. 후후. 같이 열심히 하자는 이야기 입니다.


[알림]
여러분도 아시겠지만, 마이크로 소프트웨어 잡지의 2,3,4월호에도 JMF를 이용한 방송국 프로그램에 대한 강좌가 있더군요. 저도 읽어보았고 소스분석도 다 마쳤습니다. 일단은 목적이 인터넷 방송 선국장치를 통한 간략한 방송 프로그램의 작성이구요.. 네에.. 처음 JMF를 시작하시는 분들은 꼭 읽어보세요.. 자료도 별로 없을때 이렇게 연재를 시작한것은 정말 우리같은 개발자에게 도움이 됩니다. 아쉽다면 영상부분의 몇몇 설명을 그냥 건너뛴 점이구요.. 하여간 관심있는 분들 꼭 읽어보세요. 나중에 저도 시간나면 마이크로 소프트웨어의 JMF 강좌를 재 구성해서 괜찮은 프로그램 하나 만들어 올리도록 하죠.. 음.. 시간이 될라나 모르겠지만...


자. 이번 시간에는 Sun의 JMF 공식 홈페이지에 있는 Solution 예제중에서 우리에게 꽤나 필요한 프로그램인 Clone.java에 대해 알아보려고 합니다.
일단 먼저 소스 프로그램을 다운 받으시고, 실행해 보세요.. 

주어진 문제
DataSource로 부터 Source Clone을 생성하고, 미디어의 플레이백을 위해서 서로다른 Player를 생성한다. 

해결책
아래에 간단한 샘플 애플리케이션이 있습니다. 이것은 플레이백을 위한 입력 미디어의 URL을 실행인자로 받아들이고, 플레이백을 위해 얼마나 많은 복사본을 생성할지의 갯수를 받아들입니다. 실제 프로그램은 아래의 단계를 따라 수행됩니다. 

  1. 주어진 입력 URL을 통해서 DataSource를 생성하기 
  2. Manager.createCloneableDataSource를 이용해서 원본 DataSource로부터 cloneable DataSource를 생성하기 
  3. cloneable DataSource로부터 clone 생성하기 
  4. cloneable DataSource 와 각각 생성된 clones에 대하여 playback를 위한 player를 만들기 
 
  • 필요조건
  • 관련 클래스들 
  • 프로그램 구동 방법
  • 소스 코드
필요조건
Platform: JDK 1.1.6 or later
JMF API: 2.0 or later
Implementation: AJ, WPP, SPP *

* AJ = All Java, WPP = Windows Performance Pack, SPP = Solaris Performance Pack

관련클래스
  • javax.media.Player
  • javax.media.Manager
  • javax.media.protocol.SourceCloneable
프로그램 구동 방법
java Clone <URL> <# of copies>


Example:
java Clone file:/c:/temp/foo.mov 2
Source Code

위의 예제는 Sun의 JMF 홈페이지에서 보실 수 있습니다. 예제의 이름은 Clone.java인데, 아주 간단한 프로그램이지만, 실제로 전 이 프로그램에서 제공된 방법을 통해서 많은 부분 도움을 받고 있습니다. 처음 JMF 할때 도대체 데이타 복제를 어떻게 해야하나.. 여러개의 플레이어를 동시에 재생하려면 어떻게 해야하나하는 의문들이 들었었는데... 바로 이 소스 코드를 보고 해결할 수 있었습니다. 그래서 예제들도 무심히 지나치지 마시고, 반드시 숙독해서 자신의 프로그램에 바로바로 적용할 수 있도록 하는 것도 좋은 방법이라고 생각됩니다.

// Clone.java

// 요구되는 패키지들의 임포트부분 
import java.awt.*;
import javax.media.*;
import javax.media.control.TrackControl;
import javax.media.format.*;
import javax.media.protocol.*;

import java.net.*;
import java.io.*;

/**
* cloneable DataSource를 이용해서 실제 dataSource를 복제하고 그 결과로 playback을 수행하는 
* 애플리 케이션 입니다. 
* 이 프로그램은 Sun에서 제공된 예제 입니다.  
*/


// 데이타 소스의 복제를 위해 필요한 기능을 수행하는 클래스를 하나 만들었습니다.
// 이 클래스 내부에서 복제하고자 하는 데이타 소스를 인자로 넘겨받아서 Player를 생성하고 
// 생성된 Player에 대해 이벤트 처리를 하는 부분입니다.
// 주의 할것은, 단순히 player만 생성한후에 이벤트 처리와 함께 player의 states 처리를 제대로 하지 않으면
// 프로그램 구동후 얼마안가서 바로 해석하기도 힘든 에러가 발생한다는 점입니다. 
// 잊지 마세요.. 이벤트 처리 및 Player의 states 처리!!!

public class Clone extends Frame implements ControllerListener {

    Player p;        // Playback을 위한 Player

    Object waitSync = new Object();
    boolean stateTransitionOK = true;


    /**
    * 데이타소스가 주어지면, player를 생성하고, 생성된 player를 이용해서 
    * playback을 하기위해서 사용합니다. 
    */

    public boolean open(DataSource ds) {

   
    // 데이타소스의 타입이 어떠한 것인지를 먼저 알아봅니다. DataSource의 getContentType()를 이용합니다.
        // 이때 리턴되는 값은 데이타 소스의 미디어 타입에 대한 String 입니다.
        // 예를 들면 create player for: RAW 이와 같은 형태입니다.

        System.err.println("create player for: " + ds.getContentType());

        // player를 만들기 위해서 Manager의 createPlayer를 호출합니다.
        // 이때 넘겨주는 인자가 바로 데이타 소스 입니다. 
        try {
            p = Manager.createPlayer(ds);

        } catch (Exception e) {
            System.err.println("Failed to create a player from the given DataSource: " + e);
            return false;
        }

        // player의 생성이 성공했다면 player에 대한 이벤트 처리를 등록하여야 합니다.
        p.addControllerListener(this);


        // player를 Realize 단계까지 이동하도록 만들어 주어야 합니다.
        // 여기서 주의할것은 player를 생성한후 바로 start를 호출하지 말고 
        // 현재 player의 상태를 체크해서 각 상태에 알맞도록 처리를 해주어야 한다는 것입니다. 
        // player의 prefetch를 호출함으로서 player에게 prefetched 상태로 이동하도록 명령합니다.

        p.prefetch();

        // player가 원하는 상태에 도달되도록 기다립니다.
        if (!waitForState(p.Prefetched)) {
            System.err.println("Failed to realize the player.");
            return false;
        }

        // Realize 상태까지 완료되었다면 이제 우리는 이 부분에서 
        // VisualComponent와 ControlPanelComponent를 추가하여 사용자에게 시각적인 효과를 
        // 나타낼 수 있습니다. 
        // 이 부분에서는 일단 레이아웃을 변경했구요..
        setLayout(new BorderLayout());

        // visual component를 얻기위해서 선언했구요
        Component cc;

        // controlPanelComponent를 얻기 위해서 선언했습니다. 
        Component vc;
        if ((vc = p.getVisualComponent()) != null) {
            add("Center", vc);
        }

        if ((cc = p.getControlPanelComponent()) != null) {
            add("South", cc);
        }

        // 자. 여기까지 해서, 시각적인 컴포넌트의 등록까지 마쳤습니다.
        // 이제 남은것은 player를 start 시키는 일 입니다. 

        p.start();

        // 화면에 보이도록...
        setVisible(true);

        return true;
    }

    public void addNotify() {
        super.addNotify();
        pack();
    }

    /**
    * Player로 하여금 주어진 상태로 이동할때까지 Blocking을 합니다. 
    * 만약 player의 상태변화가실패하면 false가 리턴됩니다. 
    * 아래에 구현된 while에 의한 방법이 그래도 가장 안전한 방법이네요...
    */

    boolean waitForState(int state) {
        synchronized (waitSync) {
            try {
                while (p.getState() < state && stateTransitionOK)
                waitSync.wait();
            } catch (Exception e) {}
        }
        return stateTransitionOK;
    }


    /**
    * Controller에 대한 이벤트 리스터 부분입니다. 
    * 이 부분들은 예전부터 강좌에서 많이 설명드렸으니 어렵지 않죠??
    */
    public void controllerUpdate(ControllerEvent evt) {

        if (evt instanceof ConfigureCompleteEvent ||
            evt instanceof RealizeCompleteEvent ||
            evt instanceof PrefetchCompleteEvent) {
            synchronized (waitSync) {
                stateTransitionOK = true;
                waitSync.notifyAll();
            }
        } else if (evt instanceof ResourceUnavailableEvent) {
            synchronized (waitSync) {
                stateTransitionOK = false;
                waitSync.notifyAll();
            }
        } else if (evt instanceof EndOfMediaEvent) {
            p.close();
            //System.exit(0);
        } else if (evt instanceof SizeChangeEvent) {
        }
    }



    /**
    * Main program 부분입니다. 애플리케이션이니까 당근이죠..
    * main문의 인자로 플레이백을 위해 필요한 미디어 파일의 이름과 몇개의 데이타 소스를 
    * 복제할것인가에 대한 갯수를 넘겨주어야 합니다.
    */

    public static void main(String [] args) {

        if (args.length == 0) {
            prUsage();
            System.exit(0);
        }

        MediaLocator ml;
        int copies = 1;

        // 파일이름을 스트링형태로 받아서 MediaLocator를 생성합니다.
        if ((ml = new MediaLocator(args[0])) == null) {
            System.err.println("Cannot build media locator from: " + args[0]);
            prUsage();
            System.exit(0);
        }

        if (args.length > 1) {
        try {
            copies = new Integer(args[1]).intValue();
        } catch (NumberFormatException e) {
            System.err.println("An invalid # of copies is specified: " + args[1]);
            System.err.println("Will default to 1.");
            copies = 1;
        }
    }

    DataSource ds = null;

    // MediaLocator를 통해서 원하는 미디어 파일의 위치를 알아내었죠?
    // 이제 그렇게 알아낸 위치로부터 데이타 소스를 생성해 내야 합니다.
    // 바로 , Manager의 createDataSource를 호출합니다.
    // 물론 이때 넘겨주는 인자는 MediaLocator가 되겠지요..
    // 이 부분에 한 작업이 바로 원본 데이타소스를 만드는 과정이었구요..

    try {
        ds = Manager.createDataSource(ml);
    } catch (Exception e) {
        System.err.println("Cannot create DataSource from: " + ml);
        System.exit(0);
    }

    // 자. 이부분이 바로 데이타소스 복제의 핵심입니다.
    // Manager의 createCloneableDataSource를 이용해서 원본 데이터를 넘겨주면 
    // 원본 데이타소스에 대한 복제본 데이타 소스가 리턴되어 나옵니다.
    // 이 리턴된 복제본 데이타 소스를 이용해서 우리가 원하는 player를 만들고 playback을 하기위해서 
    // 위에서 만든 Clone 클래스로 복제본 데이타 소스를 넘겨주는 것입니다. 

    ds = Manager.createCloneableDataSource(ds);    // 정말 주의!! 이부분 확인하세요.. 아래에 정리했습니다. 

    if (ds == null) {
        System.err.println("Cannot clone the given DataSource");
        System.exit(0);
    }

    Clone clone = new Clone();


    // 복제본 데이타 소스 클래스는 위에서 만들었고, 복제된 데이타 소스를 넘겨줍니다.
   
// 이렇게 해서 player가 생성되도록 해야겠죠..
    
    if (!clone.open(ds))
        System.exit(0);

    // player를 여려개 만들려고 하는경우
    // 아래와 같이 루프를 돌려서 새롭게 player를 만들고 이벤트 처리를 위해서 
    // 원하는 갯수만큼 새롭게 Clone 클래스를 만들어 주어야 합니다. 

    for (int i = 1; i < copies; i++) {
        clone = new Clone();
        if (!clone.open(((SourceCloneable)ds).createClone()))
            System.exit(0);
        }
    }


    static void prUsage() {
        System.err.println("Usage: java Clone <url> <# of copies>");
    }
}

///////////////////// program End

SourceCloneable은 interface 로서, cloneable되어지는 경우에 반드시 implements되어야 합니다. 
cloneable DataSource를 생성하기 위해서는 Manager.createCloneableDataSource 를 이용해야 합니다.

 DataSource createClone()
          이 함수는 동일한 데이타 스트림의 복사본을 제공하기 위해 원본 DataSource의 clone을 생성하도록 해줍니다.
 

Method Detail

createClone

public DataSource createClone()
Create a clone of the original DataSource that provides a copy of the same data streams. The clones generated may or may not have the same properties of the original DataSource depending on the implementation. Therefore, they should be checked against the properties required for the application.
For example, the original DataSource may be a "pull" DataSource (PullDataSource or PullBufferDataSource). But the resulted clone may be the equivalent "push" DataSource. In that case, the resulting "push" DataSource will push data at the same rate at which the original DataSource is being pulled.
Returns:
a clone of the DataSource, or null if a clone could not be created.


createCloneableDataSource

public static DataSource createCloneableDataSource(DataSource source)
Creates a cloneable DataSource. The returned DataSource implements the SourceCloneable interface and enables the creation of clones by the createClone method.

If the input DataSource implements SourceCloneable, it will be returned right away as the result. Otherwise, a "proxy" DataSource is created. It implements the SourceCloneable interface and can be used to generate other clones.

[전 처음에 아랫부분 무시했다가 엄청 혼났습니다. 주의하세요.. ]
When createCloneableDataSource is called on a DataSource,
the returned DataSource should be used in place of the original DataSource.
Any attempt to use the original DataSource may generate unpredictable results.

The resulted cloneable DataSource can be used to generate clones.

The clones generated may or may not has the same properties of the original DataSource depending on the implementation. Therefore, they should be checked against the properties required for the application. If the original DataSource is not SourceCloneable and a "proxy" DataSource is resulted, the clones generated from this "proxy" DataSource is of type PushDataSource or PushBufferDataSource depending on the type of the original DataSource. In this case, each clone pushes data at the same rate that the original DataSource is pulled or pushed.
Parameters:
source - the DataSource to be cloned
Returns:
a cloneable DataSource for the given source

[정리]
그다지 어려운 소스 프로그래은 아닙니다만, 정말 중요한 내용이 많이 들어있었습니다. 그래서 특별히 이번소스를 분석해서 강좌에 넣게 되었구요. 이 부분에서 주의할 것은 원본 데이타 소스에 대해서 복제본을 만들기 위해서 Manager.createCloneableDataSource를 호출하였고, 리턴된 값이 바로 복제본 데이타 소스가 되는 것입니다. 이렇게 복제된 데이타 소스를 가지고, 원하는 갯수만큼의 player를 만들고 이벤트 처리를 하게 된것이지요.  현재 이 프로그램의 필요성이 없을런지는 몰라도..  아참. 바로 이전강좌에서도 제가 이 clone 클래스를 이용했거든요.. 기억이 나시나요? 한번 이전 강좌를 다시 읽어봐주세요..

또 하나 주의할 점은 Manager.createCloneableDataSource를 통해서 복제 가능한 데이타 소스를 생성했다면, 더 이상 원본 데이타 소스를 이용해서는 안되고, 원본 데이타 소스가 이용되는 부분에는 복제 가능한데이타 소스로 전부 바꾸어져야 한다는 것입니다. 실제로 제가 이것때문에 처음에 프로그램이 이유없이..(??) 다운되고, 시스템도 재부팅되고.. 하여간 정말 고생많았지요.. 언제나 도움말을 확실히 읽고 넘어가자구요.. 결국 도움말에 나와있더라구요.. 쩝...

 

[ 질문에 대한 답변]

아래 질문에 대한 답변은 함께 공유하면 좋을듯 싶어서... 올립니다. 지난 번에 어느분이 매일로 문의하신 내용인데요..

문의 : 제가 프로그램 돌릴때는 아래와 같은 에러 메시지만 나오고 프로그램이 중단됩니다. 예제는 Sun에서 다운로드 받은것도 있구요. 훈님의 강좌예제중에서 캡쳐해서 저장하는 예제도 아래와 같은 에러가 납니다.

Unable to realize com.sun.media.MediaEngine@62ac60a3 coudn't realize processor


답변: 사실 처음 프로그래밍할때 이런 에러가 나오면 정말 난감합니다. 도대체 이렇게 에러 메시지가 나오면 어디에서 찾아 보아야 하나요.. 쩝.. 그렇죠? 그런데 잘 보니까 MediaEngine 이라는 부분이 있네요.. 그리고 processor가 realize가 안되었다는 이야기이구요.. 사실 이것만 봐서는 어떤곳이 에러인지 정말 찾기가 어렵습니다. 그래서 프로그램 실행할때 생성되는 jmf.log 파일을 보내달라고 했습니다. 그래서 확인한 사항인데... 보내준 jmf.log 파일을 보니까... 주루루룩 설명이 있다가 아래와 같은 메시지가 있더군요..

## Connect to multiplexer
## Output content type : AVI

XX Failed to find a multiplexer for the specified output
XX Failed to realize : com.sun.media.MediaEngine@17c05de7
XX Cannot connect the multiplexer


자. 보셨나요.. 이곳에서도 MediaEngine 이라고 나옵니다. 그리고 그 밑에 processor가 realize되지 않는다고 나오지요. 그럼 그 윗부분을 보세요..  Connect to multiplexer부분에서 우리가 지정한 타입이 AVI 입니다. 즉, 예제 프로그램에서 AVI로 저장할 타입을 지정했는데.. 프로그램을 구동시키는 시스템에서는 특정타입을 AVI 변환해주는 Multiplexer를 찾을수 없다는 이야기입니다. 결국은 , 이러한 문제의 근본 원인은 우리가 프로그램에서 지정해준 압축 포맷이 프로세서의 CODEC에 의해서 인식되지 못한다는 점이지요.. 많은 분들이 윈도우 환경에서 AVI로 저장할때 이런 문제점을 많이 본다고 합니다. 이런 경우 결국은 프로그래머가 스스로 Codec을 만들던가( 좀 힘들겠죠?) 아니면 다른 파일포맷 (예를들어 .mov , .mpg)등으로 저장하도록 변경해야 합니다.
결국은 구동하는 시스템에 processor가 인식하는 multiplexer가 제대로 있는가의 문제입니다.

이부분에 대한 설명은 제 강좌의 JMFRegistry 부분에서도 설명이 되어 있습니다. 참조해보세요.


위의 그림에서 보는바와 같이 일단 원본 데이타소스인 A로부터 복제가능한 데이터소스인 B를 만들고 나면, A는 더이상 사용할 수가 없고, A가 필요한 자리에는 모두 B로 대치되어야 합니다. 이렇게 복제가능한 데이타 소스인 B가 만들어지면, 이 B를 가지고 player를 하나 만듭니다. 그 후에 2개의 player를 더 만들고 싶은경우, 원본 데이타 소스인 A를 이용하는 것이 아니라, 복제 가능한 데이타 소스인 B로부터 createClone 함수를 이용하여 계속해서 복제를 해서 사용해야 합니다.  주의하세요...

'java UI' 카테고리의 다른 글

quartz 를 이용하기  (0) 2008.01.22
File Connection Optiobal Package  (0) 2006.02.28
jtable tutorial  (0) 2005.02.16
The Eclipse runtime options  (0) 2005.02.15
www.eclipse-plugins.info 의 순위  (0) 2005.02.12
Posted by '김용환'
,