본문

Jrtp라이브러리를 사용하여 Java에서 RTP사용하기

Java를 통하여 RTP패킷을 만드려고 찾던중 jlibrtp가 있었다. 하지만 가장 최신 버전인 0.2.2 버전은 2007년에 만들어진것이고, 그것도 테스트중에 불안정한 모습을 보여주어 다른 라이브러리를 찾아야 했다. 그러던 중 발견한것이 Jrtp(C++로 구현된 JRTPLIB와는 전혀 다르다)이다. 이 프로젝트는 NIST에서 진행된 것이며, 미국 정부기관에서의 연구결과인만큼 public domain에 속하므로 자유롭게 사용할 수 있다.

Repository(https://svn.java.net/svn/jrtp~svn)에서 checkout하고 나서 TestApplication.java를 실행시키면 동작하는것을 볼 수 있다. 두 단말간에서 파일을 RTP를 통하여 주고받는건데, 필요하지 않는 부분이 많아서, 따로 빼서 설명하려 한다. 메인페이지에 나와있듯(JRTP is a minimal RTP stack implementation) 패킷을 까보면 매우 단순한 형태의 RTP가 왔다갔다 하는것을 볼 수 있다.


* TestApplication.Java : 242 ->  RTP 세션 설정.
// RtpManager를 생성한다. 인수로는 현재 단말기의 IP가 들어간다.
rtpManager = new RtpManager(networkParameters.getIpAddress());

// 목적지가 다수인 경우를 위하여 for을 돌린다. 하지만 이 프로그램은 1:1 통신이라 별로 소용이 없다
for (int i = 0; i < destinations.size(); i++) {
    NetworkParameters dest = (NetworkParameters) destinations.get(i);

    String remoteIpAddress = dest.getIpAddress();
    int remoteRtpRecvPort = dest.getRtpRecvPort();
    int myRtpRecvPort = networkParameters.getRtpRecvPort();

    rtpSession = rtpManager.createRtpSession(myRtpRecvPort, remoteIpAddress, remoteRtpRecvPort);
    rtpSession.addRtpListener(this);
    rtpSession.receiveRTPPackets();

}

TestApplication은 RtpListener를 구현하며, handleRtpPacketEvent 메소드가 존재하므로, 위의 addRtpListener()으로 현재 자신의 객체가 리스너로 지정되기 떄문에 패킷 수신시 handleRtpPacketEvent 메소드가 호출된다. 아래의 소스에서 이를 확인할 수 있다. NetworkParameters는 재사용 가능한 형태로 제작되었지만 프로그램 구성에서는 복잡하게 값을 설정해둬서 첫인상이 좋지 않던 클래스다. (특히 ConfigurationLoader쪽에서 값 설정할때) recieveRTPPackets()는 RtpPacketReciever 스레드를 실행시킨다.


* TestApplication.java : 351 => 패킷 수신
public
void handleRtpPacketEvent(RtpPacketEvent rtpEvent) {

    // Print the remote IP address and port
    RtpSession rtpSession = (RtpSession) rtpEvent.getSource();

    RtpPacket rtpPacket = rtpEvent.getRtpPacket();

    textArea.append("Received RTP packet #" + rtpPacket.getSN() + "\n");
    textArea.setCaretPosition(textArea.getDocument().getLength());

    System.out.println("---------------\n[TestApplication] RTP Data:");
    System.out.println("[TestApplication] Received V: " + rtpPacket.getV());
    System.out.println("[TestApplication] Received P: " + rtpPacket.getP());
    System.out.println("[TestApplication] Received X: " + rtpPacket.getX());
    System.out.println("[TestApplication] Received CC: " + rtpPacket.getCC());
    System.out.println("[TestApplication] Received M: " + rtpPacket.getM());
    System.out.println("[TestApplication] Received PT: " + rtpPacket.getPT());
    System.out.println("[TestApplication] Received SN: " + rtpPacket.getSN());
    System.out.println("[TestApplication] Received TS: " + rtpPacket.getTS());
    System.out.println("[TestApplication] Received SSRC: " + rtpPacket.getSSRC());
    System.out.println("[TestApplication] Received Payload size: " + rtpPacket.getPayloadLength());

}

RrtpPacketReciever에서 데이터를 수신하면 RtpPacketEvent 이벤트를 리스너들에게 보내는데, 이로서 TestApplication.handleRtpPacketEvent가 호출된다. rtpEvent에는 rtpSession, rtpPacket정보가 들어가므로 이를 꺼내서 적절히 사용자에게 제공하는 것이 목표이며, 위와 같이 사용할 수 있다. 그리고 또한 rtpPacket.getData()를 통하여 byte[]형태의 데이터를 받아올 수 있다.


* StreamFile.java : 79 => 패킷 발신
int
bufferSize = RtpPacket.MAX_PAYLOAD_BUFFER_SIZE;
byte[] buffer = new byte[bufferSize];

FileInputStream fileInputStream = new FileInputStream(application.selectedFile);

// Set up a test RTP packet
RtpPacket rtpPacket = new RtpPacket();
rtpPacket.setV(1);
rtpPacket.setP(1);
rtpPacket.setX(1);
rtpPacket.setCC(1);
rtpPacket.setM(1);
rtpPacket.setPT(1);
rtpPacket.setTS(1);
rtpPacket.setSSRC(1);

int numBytesRead = 0;
long totalBytesRead = 0;
long fileSize = application.selectedFile.length();
long startTime = System.currentTimeMillis();

// Read file
while ((numBytesRead = fileInputStream.read(buffer)) > 0) {
    totalBytesRead += numBytesRead;
    application.statusLabel.setText("Sending " + totalBytesRead  + " of " + fileSize + " bytes");

    if (application.streaming == false) {
        try {
            System.out.println("[StreamFile] paused");
            synchronized (this) {
                wait();
            }
        } catch (InterruptedException ie) {
            System.out.println("[StreamFile] continuing");
        }
    }

    // Set RTP packet data rather than create new RTP packet
    rtpPacket.setPayload(buffer, numBytesRead);

    try {
        application.rtpSession.sendRtpPacket(rtpPacket);
        try {
            Thread.sleep(RATE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    } catch (RtpException re) {
        re.printStackTrace();
    }
}

// Clean up
application.writeScroll(application.textArea, " Done.\n");
fileInputStream.close();
파일 스트림으로부터 자료를 읽어서 RTP 패킷으로 전송하는 부분이다. 다음 게시물에서 자세하게 설명하겠지만 RTP 패킷은 우선 위와같이 설정해준다. Sequence Number를 제외한 모든 부분을 1로 설정하고 setPayload로 패킷의 내용을 채운 후 sendRtpPacket()으로 RTP 패킷을 보낸다. 패킷을 보내고 나서 다음 버퍼를 보내기 전, RATE(20ms)만큼 쉬게 해주어서 타이밍 문제를 지나치려 했다. 이부분은 손을 좀 봐주면 좋을것 같다.

댓글

Holic Spirit :: Tistory Edition

design by tokiidesu. powerd by kakao.