RTSP로 전송되는 CCTV영상을 브라우저에서 스트리밍하는 과정에 대해 정리하고자 합니다.
요구사항은 간단했습니다. RTSP URI를 이용하여 브라우저에서 스트리밍 하는 것입니다.
단, 이때 스트리밍되는 데이터들은 백엔드를 거쳐서 프론트엔드로 전송되어져야 합니다.
기존방식 - HLS로 스티리밍
애초에 브라우저에서는 RTSP 프로토콜을 지원하지 않기 때문에, RTSP URI를 브라우저에서 직접 사용할 수는 없습니다.
이 방식은 RTSP로 받은 데이터들을 브라우저에서 지원하는 HLS 방식으로 convert(m3u8파일들)해서 해당 파일들을 프론트엔드로 전송해주는 방식이었습니다.
참고로 m3u8파일은 아래와 같은 구조를 가지고 있습니다.
// 이 파일이 M3U8플레이 리스트 라는 것을 의미
#EXTM3U
// playlist가 따르는 hls 프로토콜 버전을 의미
#EXT-X-VERSION:3
// 각 media segment가 가지는 maximum duration을 의미
// 여기에서는 8초를 의미
#EXT-X-TARGETDURATION:8
// 재생 목록에서 첫 media segment가 가지는 시퀀스 번호를 의미
#EXT-X-MEDIA-SEQUENCE:1
// EXTINF는 아래 media segment의 duration을 의미
#EXTINF:8.333333,
80612e35-f8e6-4364-b9d7-05c62ca810b31.ts
#EXTINF:8.333333,
80612e35-f8e6-4364-b9d7-05c62ca810b32.ts
#EXTINF:8.333333,
80612e35-f8e6-4364-b9d7-05c62ca810b33.ts
#EXTINF:8.333333,
80612e35-f8e6-4364-b9d7-05c62ca810b34.ts
#EXTINF:2.900000,
80612e35-f8e6-4364-b9d7-05c62ca810b35.ts
//playList가 끝났다는 것을 의미
#EXT-X-ENDLIST
문제점
이 방식의 가장 큰 단점은 높은 latency입니다. 실제로 구현된 방식은 실제 영상과 최소 3초에서 6초정도의 지연시간이 발생했습니다.
따라서 위 방식의 구현방안은 자세히 다루지 않고, 어떻게 이러한 지연시간들을 없앨 수 있었는지 알아보도록 합시다.
1차시도 - WebSocket을 이용하여 image로 받아오기
HLS방식의 문제 분석
HLS방식에서 지연시간이 생기는 이유는 RTSP로 전송된 데이터들을 m3u8파일로 변환하고이 파일들이 변환되면,
프론트엔드로 보내는 일련의 나름 복잡한 과정이 있었기 때문이었습니다.
그래서 저는 rtsp로부터 높은 프레임으로 image들을 받아와서 이걸 바로 프론트엔드로 전송하여 마치 영상처럼 보여주는 방식을 택했습니다.
실제로 이 방식을 통해서 latency문제는 해결할 수 있었습니다.
새로운 문제점
하지만, 화질이 원본 영상보다 많이 떨어지는 단점이 생겼다.
최종 방법 - WebSocket을 이용하여 mpeg.ts 전송
순간 이런 생각이 들었습니다. 아니 WebSocket으로 바로 파일을 전송할 거면 jaffree를 이용해서 파일을 변환한 다음에 바로 전송해서 플레이 하면 안되나?
최종적으로 jaffree를 이용하여 mpeg.ts파일들로 변환한 뒤 바로 프론트로 전송해주고,(이 과정에서 reactive-websocket handler 인터페이스를 implement하여 사용했습니다.)
프론트엔드에서는 mpeg.ts파일을 재생가능한 jsmpeg-player를 이용하여 플레이 해주는 방식을 택했습니다.
아래 코드는 jaffree의 ffmpeg을 이용하여 rtsp프로토콜 데이터들을 입력받아 mpeg.ts파일들로 변경하는 과정을 보여준다.
FFmpegResultFuture result = FFmpeg.atPath()
.setOverwriteOutput(true)
.addInput(
UrlInput.fromUrl(rtspUrl)
.addArguments("-rtsp_transport", "tcp")
.setReadAtFrameRate(true)
)
.addOutput(
PipeOutput.pumpTo(websocketOutput)
.setFormat("mpegts")
.setCodec(StreamType.VIDEO, "mpeg1video")
.setFrameSize(dWidth, dHeight)
.addArguments("-vf", getVFOptionStr(dWidth, dHeight))
.addArguments("-q:v", qScale)
.addArguments("-b:v", bitrate)
.addArguments("-maxrate:v", maxBitrate)
.addArguments("-bufsize", bufsize)
).executeAsync();
위 방식을 이용해서 지연시간을 없애고, 원본화질과 거의 흡사한 화질을 가진 영상을 브라우저에서 재생할 수 있었습니다.
TroubleShootings
테스트를 하다보니 몇몇 RTSP URI은 정상작동 되지만, 몇몇은 재생이 되지 않았습니다.
코덱 문제인지 알았으나, 그 이유는 FFmpeg에서 default로 udp 통신을 하기 때문에 생기는 문제였습니다.
FFmpeg에 아래 옵션을 추가하니 정상적으로 작동했습니다.
'-rtsp_transport', 'tcp',
위 코드에서 .addArguments("-rtsp_transport", "tcp") 부부인 그것입니다.
혹시나 잘못된 사항이나 질문사항이 있다면, 댓글로 남겨주시면 감사하겠습니다!
'java,springboot' 카테고리의 다른 글
[Springboot] Reactive Redis 총 정리(config, generic, test) (1) | 2024.02.06 |
---|---|
try-with-resources 사용법 및 주의점 (1) | 2023.12.05 |
[R2DBC] Batch Insert 성능 테스트 및 개선 (17배 향상) (0) | 2023.09.05 |
[Solved] Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor 에러 해결 (0) | 2023.06.13 |
[WebFlux] saveAll(Iterable) vs saveAll(Flux) 뭘 써야 할까? (0) | 2023.05.23 |