All Articles

Video Libraries/Live555

H.264, H.265 RTSP Streaming을 위한 Live555 Wrapper 구성: (1) 개요 및 example 소스 분석 및 설계

1. Live555를 쓰게된 이유

Live555를 이용하게된 이유는 해외 대형 디스플레이 장치에서 방문 학생으로 프로젝트를 진행.

따라서, 처음에는 FFmpeg으로 RTSP 스트리밍을 시도했지만 몇몇 문제가 있어서 FFserver 고려했지만 지원이 중단. Nimble Streamer등의 RTSP 서버들은 현재 진행하고 있는 프로젝트에서 사용하기에는 지연이 최소화되지 않음을 발견. 여러 곳에서 정보를 얻어서 Live555로 진행을 하게됨. VLC나 다른 비디오 프로그램 등에서도 널리 쓰이는 걸로 알고있음. 

구상한 비디오 서버 시스템 디자인(클라이언트는 추후)

위 그림에서 Live555 Streamer처럼 동작하는 멀티 인풋, 멀티 아웃풋 구조의 모듈을 구성할 예정. 설명이 잘 안되있고 대부분 override, function call back하는 객체, 함수들로 이루어져있어서 H.264/AVC, H.265/HEVC를 다루는 클래스들을 오버라이딩해서 래퍼 클래스를 만들어서 쓸 예정. 지연 문제 때문에 UDP 기반의 처리로할 예정이지만 상황에 따라 TCP 기반 처리도 고려.

Live555 관련해서 Windows에서 nmake를 이용해서 처리를하려는데 잘안되서 빈프로젝트에 넣어서 라이브러리로 빌드해서 사용. 라이센스는 Lesser GPL. 소스 코드는 여기서 2019.11.28 릴리즈된 버전을 사용.

 

2. 주요 객체 코드 분석

이 게시물에서는 객체와 중요 함수 위주로 분석.

예제 소스: Live555/testProgs/testH265VideoStreamer.cpp

// Define task scheduler and usage environment
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
UsageEnvironment* env    = BasicUsageEnvironment::createNew(*scheduler);

TaskScheduler는 이벤트 트리거들과 프로세스의 타이밍 전반적인 부분을 관리. 생성과 동시에 프로세스 타임 시작.

UsageEnvironment는 scheduler를 핸들하고 콘솔에 메시지를 남기는 역할.

// Define RTP and RTCP
const Port rtpPort(RTP_PORT_NUMBER);
const Port rtcpPort(RTCP_PORT_NUMBER);

Groupsock rtpGroupsock(*env, destinationAddress, rtpPort, ttl);
rtpGroupsock.multicastSendOnly(); // we're a SSM source
Groupsock rtcpGroupsock(*env, destinationAddress, rtcpPort, ttl);
rtcpGroupsock.multicastSendOnly(); // we're a SSM source

OutPacketBuffer::maxSize = 100000;

// Create 'RTP Sink'
RTPSink* VideoSink = H265VideoRTPSink::createNew(*env, &rtpGroupsock, 96);

// Create (and start) a 'RTCP instance' for this RTP sink:
const unsigned estimatedSessionBandwidth = 500; // in kbps; for RTCP b/w share
CNAME[maxCNAMElen] = '\0'; // just in case
RTCPInstance* rtcp = RTCPInstance::createNew(
    *env, &rtcpGroupsock, estimatedSessionBandwidth, HOST_NAME,
    videoSink, NULL /* we're a server */, True /* we're a SSM source */);

RTP는 단방향으로 데이터를 전송하는 역할을하고 RTCP는 양방향으로 패킷 손실, 세션에 대한 정보를 레포트한다.

이 예제에서는 멀티캐스트를 위해 IPv4의 SSM 어드레스를 목표하는 주소로 송신할 수 있는 소켓을 열어준다.

따라서, 비디오 하나를 아웃풋으로 스트림하기위해서는 RTP와 RTCP에 해당하는 소켓 2개를 생성해주어야한다.

RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554);
ServerMediaSession* sms = ServerMediaSession::createNew(
    *env, "testStream", inputFileName,
	"Session streamed by \"testH265VideoStreamer\"", True /*SSM*/);
sms->addSubsession(PassiveServerMediaSubsession::createNew(*videoSink, rtcp));
rtspServer->addServerMediaSession(sms);

RTSP는 스트리밍 시에 플레이백과 같은 스트리밍 데이터를 제어한다.

클라이언트에서의 커넥션은 RTSP의 주소에 연결되며, 클라이언트의 비디오(RTSP에 정의되어있는 세션들), 프로토콜(UDP/RTP 등), 포트 번호 요청에 따라 session 번호를 넘겨주게 된다.

앞선 서버 설계는 다중 아웃풋을 지원하려고하므로 RTSPServer와 ServerMediaSession을 한쌍으로 정의하고 ServerMediaSession에 비디오 하나당 Subsession을 하나 생성하여 ServerMediaSession에 추가하는 방식으로 설계한다. 클라이언트에서 별도로 RTSPServer에 어떤 비디오들이 있는지 확인하는 작업을 수행안한다면, (1)알 수 없거나, (2) 미리 정의하여 RTP, RTCP 포트와 매칭한다.

void play() {
  // Open the input file as a 'byte-stream file source':
  ByteStreamFileSource* fileSource
    = ByteStreamFileSource::createNew(*env, inputFileName);
  if (fileSource == NULL) {
    *env << "Unable to open file \"" << inputFileName
         << "\" as a byte-stream file source\n";
    exit(1);
  }

  FramedSource* videoES = fileSource;

  // Create a framer for the Video Elementary Stream:
  videoSource = H265VideoStreamFramer::createNew(*env, videoES);

  // Finally, start playing:
  *env << "Beginning to read from file...\n";
  videoSink->startPlaying(*videoSource, afterPlaying, videoSink);
}

void afterPlaying(void* /*clientData*/) {
  *env << "...done reading from file\n";
  videoSink->stopPlaying();
  Medium::close(videoSource);
  // Note that this also closes the input file that this source read from.

  // Start playing once again:
  play();
}

Play 함수 내부 프로세스는 파일 소스(파일을 읽을 수 있는 객체), 프레임 소스(전달가능한 프레임으로 바꾸는 객체), 스트리밍 시작(RTPSink를 통해 송신하는 루프)로 진행.

AfterPlaying은 송신이 완료된 후의 콜백함수이다.

 

 

3. Server Wrapper 설계

객체와 데이터 플로우

설계하는 Wrapper는 (1) UsageEnvironment, TaskScheduler, RTSPServer, ServerMediaSession을 오버라이딩 없이 객체로 포함하고 (2) Decoder Instance로부터 codec context를 받아 SubSession을 생성하여 ServerMediaSession에 추가한다 (3) Event trigger를 통해 모든 세션을 종료하는 이벤트와 일시정지 등의 이벤트를 처리하며 (4) TaskScheduler와 Session들로부터 보고된 패킷 상황으로 timing을 동기화한다. 

Frame Source는 기존 파일로부터 Frame Source를 처리하는 객체를 오버라이딩하여 패킷 버퍼로부터 읽어오는 작업을 수행한다.

RTP Sink는 NAL, Packet 단위 송신을 위해 RTPSink를 오버라이딩하여 사용한다.