PTS 개념에 대한 정리
프로젝트 중에 웹을 통해서 실시간 스트림을 재생하는 기능을 구현하는 작업이 있었다. 카메라로부터 RTSP 스트림을 받아 브라우저에서 html5 video 표준으로 재생을 하는 기능이다. 이 기능을 구현하기 위해서는 RTSP 스트림을 받아서 fragmented mp4 스트림으로 remuxing 하는 작업이 필요하다.
remuxing 작업은 ffmpeg의 example 중에 remuxing.c을 이용하면 그리 어렵지 않게 구현할 수 있다. 하지만, 실제 프로젝트에서는 ffmpeg의 입력으로 RTSP 스트림을 받는 것이 아니라, live555를 통해서 받은 payload 데이터 입력을 사용하도록 되어 있었다. 즉, remuxing을 위해서는 live555로부터 들어온 데이터를 ffmpeg의 AVPacket으로 변환 후, remuxing 예제처럼 특정 출력으로 만들어주는 작업이 필요했다.
AVPacket의 data 필드가 디먹싱 된 데이터를 요구하기 때문에, live555로 받은 subsession의 chunk 데이터를 설정해주면 미디어의 데이터를 넘겨주는 작업은 어렵지 않게 할 수 있다. 문제는 pts를 어떻게 계산해주느냐이다.
위키피디아를 통해 PTS의 의미를 살펴보면,
The presentation timestamp (PTS) is a timestamp metadata field in an MPEG transport stream or MPEG program stream that is used to achieve synchronization of programs’ separate elementary streams (for example Video, Audio, Subtitles) when presented to the viewer. The PTS is given in units related to a program’s overall clock reference, either Program Clock Reference (PCR) or System Clock Reference (SCR), which is also transmitted in the transport stream or program stream.
대충 이해한 것을 정리하자면, PTS는 비디오나 오디오, 자막 등의 MPEG 스트림의 동기를 맞추기 위해서 사용하는 시간 정보라고 할 수 있겠다. PCR이나 SCR을 기준으로 생성하게끔 되어 있다는 말도 부가적으로 있다. 대체 무엇이란 말인가.
몇 가지 자료 – 대표 자료는 이것이다. – 들을 추가적으로 찾아보고, 테스트를 해 본 결과, 다음과 같은 결과를 얻을 수 있었다.
Video PTS는 프레임 간의 시간 간격을 기준으로 단조 증가한다. 즉, 1초에 30장의 영상을 표시하는 경우, 프레임 간의 간격은 1/30초가 될 것이다. 첫 번째 프레임이 0, 두 번째는 0.033, 세 번째는 0.067,…
그런데, 한 가지 더 알아야 할 부분이 있다. PTS는 초가 아니라 timebase를 기준으로 사용한다. 1초를 몇 등분으로 보느냐라고 하는 단위 값이다. live555에서는 subsession의 rtpTimestampFrequency()
라는 함수를 통해서 넘어오는 값이고, ffmpeg에서는 AVStream의 time_base
필드가 의미하는 값이기도 하다. 결과적으로 PTS는 초 기준으로 구한 값에 timebase를 곱해준 값이 된다.
1 | Video_PTS = ${frame_index} * (1 / frame_rate) * timebase |
Audio PTS도 Video PTS와 마찬가지로 볼 수 있다. 그러나, 오디오 데이터는 비디오 데이터와 같이 frame rate를 기반으로 한 프레임씩 캡처해서 생성하는 것이 아니라, 연속적으로 한 시점에 대해서 한 바이트 이상의 값으로 샘플링하게 된다. 예를 들어, 현재 사용하고 있는 카메라로부터는 8KHz의 샘플링된 모노 채널의 오디오 데이터를 받고 있는데, 이것은 초당 8000번의 샘플이 발생한다는 의미가 된다. 그러나, 실제로 오디오 데이터를 받아보면, 1바이트씩 전송되는 것이 아니고, 일정 크기의 chunk 데이터로 넘어온다. 그러나 이 크기는 카메라마다 다른 것으로 보인다. 즉, 80 바이트씩 전송할 수도 있고, 120 바이트씩 전송할 수도 있는 것이다. 그래서, 이를 기준으로 Audio PTS를 정리해보면, 다음과 같다.
1 | Audio_PTS = ${frame_index} * (1 / chunk_size) * timebase |
이렇게 구한 값이 표준에 적합한 것인지는 확실하진 않다. 그러나, PTS 관련 문제로 여러 난관들이 있었는데, 이렇게 계산한 값이 가장 안정적인 듯하다.
개념은 이런 식으로 파악했지만, live555 로부터 frame rate
정보를 얻을 수는 없다. 그리고, 카메라의 설정이나 상태에 따라 frame rate
에 따른 프레임 간격이 일정하지 않을 수도 있다. 무슨 얘긴가 하면, 초당 10프레임 전송으로 설정을 했을 때, 0.1초 간격으로 오길 기대하나, 정작 프레임이 오는 간격이, 0, 0.067, 0.1, 0.133,… 과 같이 될 수도 있다는 것이다.
그런데, 앞서 이해했던 PTS 값 중에, ${frame_index} * (1 / frame_rate)
이나 ${frame_index} * (1 / chunk_size)
은 바로 timestamp 값과 일치한다. 결국, RTSP로부터 받는 스트림에 대해서 PTS를 구하고자 한다면,
1 | PTS = ${RTSP timestamp의 초단위 값} * timebase |
로 정리할 수 있다.
정리한 내용으로 실제로 테스트를 해보면, 카메라의 경우에는 timestamp 값이 역전이 되어서 들어오는 경우도 있고, 값 자체가 0부터 들어오지 않는다. 정리한 식에서 ${RTSP timestamp의 초단위 값}
이란 값은 첫번째 프레임이 0이라는 가정에서 시작한다. 이것이 설정되지 않는다면, 재생 시에 그 시간으로 점프를 해야만 볼 수 영상의 시작을 볼 수 있을 것이다. 또한 timestamp가 역전되는 현상을 그대로 두고 pts를 계산해서 remuxing할 때 입력한다면, 괴로운 exception을 발견하게 될 것이다.
이것을 개선하기 위해서 timestamp 값을 그대로 사용하지 않고, 첫 프레임의 timestamp 값을 기준값으로 두고, 그 이후의 프레임에 대해 기준값에 대한 차분만으로 새로운 초단위 값을 만들어낸다. 그리고, 역전이 되는 상황에서는 차분을 계산하지 않고, 이전 프레임에서 계산한 차분을 이용해서 개선할 수 있다. 역전 현상은 초반에 한두 번 나올 수 있는 것으로 보인다. 차분으로 인한 오차는 거의 무시할 수 있는 정도라고 생각한다.