본문 바로가기

3.구현/Java or Kotlin

[Java] RTP 서버 간단히 구현하기

RTP는 실시간 전송 프로토콜(Realtime Transport Protocol)로 오디오와 비디오를 실시간을 전송하기 위한 프로토콜입니다. 지연에 민감하기 때문에 처리가 가벼운 UDP로 사용하고 있다. RTP는 데이터 전송용이고 제어용으로는 RTCP를 사용합니다.
여기서는 jrtp라고 RTP전송하는 프로토콜 라이브러리에 초점을 맞춰서 다룰 예정입니다. 전송되는 내부 데이터 처리 방식에 대해서는 다루지 않습니다.

작성자: ospace114@empal.com, http://ospace.tistory.com/

들어가기

Java에서 jrtp라는 RTP를 지원하는 오픈소스 라이브러리가 있다. 실제 코드를 보면 전송하는 기본 프로토콜 구조만 구현되어 있고, 서버로서 동작하는 기능은 빈약한다. 그렇지만 구동하는데는 문제는 없다.
jrtp를 사용해서 간단하게 동작하는 기능에 초점에 집중하려고 한다.

JRTP

https://github.com/zwensoft/jrtp에서 소스코드를 받을 수 있다.
소스파일:

jrtp.zip
29.1 kB


소스 용량은 크지 않아서 소스코드를 분석하는데는 어렵지 않다.
소스코드를 jar로 만들어서 추가해도 되고 직접 소스를 포함시켜도 된다.

RTP 리스너 구현

먼저 수신 메시지를 처리할 RTP 리스너를 구현해보다. 아래는 jrtp에서 정의된 RtpListener 인터페이스이다. 총 메소드는 4가지로 단순한다. 간단한 설명도 주석으로 포함되어 있어서 어렵지 않다.

public interface RtpListener {

    /**
     * Handle the received RTP packet.
     * 
     * @param rtpEvent The received RTP packet event.
     */
    public void handleRtpPacketEvent(RtpPacketEvent rtpEvent);

    /**
     * Handle an RTP Status event.
     * 
     * @param rtpEvent The received RTP status event.
     */
    public void handleRtpStatusEvent(RtpStatusEvent rtpEvent);

    /**
     * Handle an RTP timeout event.
     * 
     * @param rtpEvent The received RTP timeout event.
     */
    public void handleRtpTimeoutEvent(RtpTimeoutEvent rtpEvent);

    /**
     * Handle an RTP error event.
     * 
     * @param rtpEvent The received RTP error event.
     */
    public void handleRtpErrorEvent(RtpErrorEvent rtpEvent);

}

4개 메소드 중에서 중요한게 handleRtpPacketEvent()이다. 여기서 중요 수신데이터에 대해서 치리한다.

public class MyRtpListener implements RtpListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(MyRtpListener.class);

    @Override
    public void handleRtpPacketEvent(RtpPacketEvent rtpEvent) {
        RtpPacket rtpPacket = rtpEvent.getRtpPacket();

        LOGGER.debug("Received RTP packet #{}", rtpPacket.getSN());
        LOGGER.debug("  payload size: {}", rtpPacket.getPayloadLength());

    }
    @Override
    public void handleRtpStatusEvent(RtpStatusEvent rtpEvent) {
        LOGGER.debug("handleRtpStatusEvent: {}", rtpEvent.getStatus().name());

    }
    @Override
    public void handleRtpTimeoutEvent(RtpTimeoutEvent rtpEvent) {
        LOGGER.debug("handleRtpTimeoutEvent");

    }
    @Override
    public void handleRtpErrorEvent(RtpErrorEvent rtpEvent) {
        LOGGER.debug("handleRtpErrorEvent: ", rtpEvent.getCause());
    }
}

서버 구동

등록된 RTP 리스너를 서버에 등록하고 실행시켜 보자.
RTP 서버는 RtpManager에 의해서 관리되며 RtpManager에서 RtpSession을 생성해서 세션을 관리한다. 그리고 세션을 생성할때 사용할 UDP 포트 번호를 넘겨주면 된다.

RtpManager rtpManager = new RtpManager();
int udpPort = 5000;
RtpSession rtpSession = rtpManager.createRtpSession(udpPort);
rtpSession.addRtpListener(new MyRtpListener());
rtpSession.receiveRTPPackets(); //Async call

마지막에 RtpSession에 있는 receiveRTPPackets()을 호출하면 서버 구동이 된다. receiveRTPPackets()는 호출되면 종료되지 않고 RTP 메시지를 계속 처리하하기 때문에 주의가 필요하다. 그렇기 때문에 다른 처리를 해야한다면 쓰레드로 구동해야 한다.

서버 종료

실행중인 RTP 서버를 종료해보자. 서버 종료위해서는 RtpManager에 의해 생성된 RtpSession 객체에 종료 메시지를 호출한다.
먼저 패킷 수신을 중지하고 세션을 종료한다.

rtpSession.stopRtpPacketReceiver();
rtpSession.shutDown();

RTP 메시지 응답

요청이 끝나면 응답을 전송해야 한다. 현재 세션에서 데이터가 처리되기 때문에 RTP 메시지를 상대방에게 전송할 때에도 RtpSession을 통해서 보내면 된다.
먼저 보낼 RtpPacket 데이터를 생성하고 RtpPacket의 sendRtpPacket()를 통해서 전송하면 된다.

byte[] bytes = ...;

RtpPacket rtpPacket = new RtpPacket();
rtpPacket.setV(2);
rtpPacket.setP(0);
rtpPacket.setX(0);
rtpPacket.setCC(1); // 시퀀스 번호처럼 사용됨
rtpPacket.setM(1);
rtpPacket.setPT(202); //200: sender report, 201: receiver report 
rtpPacket.setTS(1);
rtpPacket.setSSRC(1);
rtpPacket.setPayload(bytes, bytes.length);

rtpSession.sendRtpPacket(rtpPacket); // send a rtp packet

한가지 주의할 점은 RtpPacket에 들어갈 데이터는 직접 처리해야한다. 프로토콜 스팩을 좀더 공부해야겠다. ㅡ.ㅡ;;

RTP 클라이언트

마지막으로 클라이언트에서 RTP 메시지를 송수신 처리하는 예를 간단하게 살펴보겠다. 서버 처럼 세션을 만들고 세션을 통해서 RTP 패킷을 전송한다. 응답 메시지를 처리하려면 서버처럼 핸들러를 등록해서 처리하면 된다.

String serverIp = "..."
int localPort = 10000;
int serverPort = 2000;
long sourceId = ...;
RtpSession  rtpSession = new RtpSession(InetAddress.getLocalHost(), localPort, serverIp, serverPort);

RtpPacket rtpPacket = new RtpPacket();
rtpPacket.setV(1);
rtpPacket.setP(1);
rtpPacket.setX(1);
rtpPacket.setM(1);
rtpPacket.setPT(99);
rtpPacket.setTS(1);
rtpPacket.setSSRC(sourceId);

int bufSize = RtpPacket.MAX_PAYLOAD_BUFFER_SIZE;
int cc = 0, len = 0;
byte[] buf = new  byte[RtpPacket.MAX_PAYLOAD_BUFFER_SIZE];
while(0<(len = read(buf, bufSize))) {
    ++cc;
    rtpPacket.setCC(cc);
    rtpPacket.setPayload(buf, len);
    rtpSession.sendRtpPacket(rtpPacket);
}

rtpSession.shutDown();

결론

특별한 어려운게 없이 RTP 송수신할 수 있는 서버 구현작업은 완료되었다. 추후 서버에서 데이터 처리하는 부분을 효율적으로 처리할 수 있는 부분과 로그 출력 부분에 대한 개선이 필요해 보인다. 그것 보다도 RTP 프로토콜을 사용한 데이터 인코딩/디코딩 처리에 대해 추가적인 구현이 필요하다. 해야할게 더 많다. ㅡ.ㅡ;;; 모드 즐프하길 바랍니다.ospace.

참고

반응형