
들어가며 ― 왜 서버부터 준비해야 할까?
RTSP 클라이언트를 직접 만들어보겠다고 마음먹으면, 보통은 이런 생각이 먼저 든다.
“일단 코드부터 짜보자.”
나 역시 그랬다. 뭔가를 만들겠다고 하면 손부터 움직이는 편이라서.
그런데 RTSP는 혼자서는 아무 일도 못 한다. 구조적으로 그렇다. 반드시 서버와 미디어 소스가 있어야 한다. RTSP는 데이터를 실어 나르는 프로토콜이 아니다. 영상 파일을 직접 보내는 게 아니라, “재생해라”, “멈춰라”, “이 스트림을 열어라” 같은 제어 명령을 주고받는 역할에 가깝다. 말 그대로 리모컨이다. 그래서 테스트를 하려면 최소한 이 세 가지가 필요하다.
- 항상 떠 있는 RTSP 서버
- 그 서버에 붙어 있는 영상 소스(H.264 등)
- 결과를 확인할 수 있는 검증용 플레이어
클라이언트를 제대로 만들고 싶다면, 이 환경을 먼저 갖추는 게 훨씬 빠르다. 돌아가는 기준점이 있어야, 내가 짠 코드가 틀렸는지도 판단할 수 있으니까.
1. 왜 이런 환경이 필요한가?
RTSP 클라이언트를 개발하다 보면 이런 상황을 자주 겪는다.
- 서버가 문제인지
- 네트워크가 막힌 건지
- 내가 작성한 코드가 잘못된 건지
구분이 안 된다. 이게 제일 답답하다. 로그는 나오는데 원인이 안 보인다. 그래서 먼저 해야 할 일은 하나다.
“서버는 정상이다.”라는 확실한 상태를 만들어두는 것.
이 전제가 있어야 클라이언트 디버깅이 시작된다. 그렇지 않으면 모든 게 의심 대상이 된다.
2. 무엇을 사용할 것인가?
테스트 환경은 복잡할 필요 없다. 아래 세 가지면 충분하다.
| 구성요소 | 역할 |
| mediamtx | RTSP 서버 |
| FFmpeg | 영상 파일을 서버로 송출 (Publish) |
| ffplay | 최종 검증 플레이어 |
구성은 단순하지만, 실제 RTSP 흐름을 모두 재현할 수 있다. 테스트용으로는 꽤 이상적이다.
3. mediamtx 서버 실행
✔ mediamtx란?
MediaMTX는 가볍고 설정이 단순한 RTSP 서버다. 예전 이름은 rtsp-simple-server였고, 지금은 MediaMTX로 발전했다. Docker로 바로 실행할 수 있어서 실험용으로 부담이 없다.
서버 실행
docker run --rm -it \
-p 8554:8554/tcp \
-p 8000:8000/udp \
-p 8001:8001/udp \
bluenviron/mediamtx
컨테이너가 올라가면 RTSP 서버가 바로 대기 상태에 들어간다.
포트 의미
포트용도
| 포트 | 용도 |
| 8554 | RTSP 제어 연결 |
| 8000/8001 | RTP / RTCP 데이터 전송 |
여기서 중요한 점이 하나 있다. RTSP(제어)와 RTP(데이터)는 분리되어 있다. 처음 접하면 헷갈리기 쉽다. 제어 채널과 미디어 채널이 다르다는 걸 머릿속에 명확히 그려두는 게 좋다.
4. FFmpeg로 스트림 퍼블리시하기
이제 서버는 떠 있다. 그런데 아직 영상은 없다. RTSP 서버는 그냥 빈 껍데기다. 콘텐츠를 밀어 넣어야 한다. FFmpeg을 이용해 MP4 파일을 서버로 퍼블리시해보자.
ffmpeg -re -stream_loop -1 -i ./BigBuckBunny.mp4 \
-an -c:v copy \
-f rtsp -rtsp_transport tcp \
rtsp://127.0.0.1:8554/mystream
명령어는 길어 보이지만, 의미는 단순하다.
| 옵션 | 의미 |
| -re | 실제 재생 속도로 전송 |
| -stream_loop -1 | 무한 반복 |
| -c:v copy | 재인코딩 없이 복사 |
| -f rtsp | RTSP 포맷으로 출력 |
| -rtsp_transport tcp | RTP를 TCP로 전송 |
특히 -c:v copy는 꽤 중요하다. 재인코딩을 하지 않기 때문에 CPU 부담이 적고, 타이밍도 안정적이다. 테스트 환경에서는 이런 단순함이 오히려 도움이 된다.
왜 H.264인가?
RTSP는 제어만 담당한다. 실제 영상은 코덱으로 인코딩되어 전달된다. H.264는 거의 모든 플레이어가 지원한다. 실험용으로는 가장 무난하다. 괜히 다른 코덱을 써서 문제를 늘릴 필요는 없다.
5. 서버가 제대로 동작하는지 검증하기
이 단계를 건너뛰면 나중에 반드시 후회한다. FFplay로 직접 재생해본다.
ffplay -rtsp_transport tcp rtsp://127.0.0.1:8554/mystream
영상이 재생된다면, 적어도 다음은 정상이다.
- 서버 동작
- 퍼블리시 동작
- RTSP 세션 협상
- RTP 스트림 전송
이 상태를 확인하고 나서야 클라이언트 코드를 만질 수 있다. 기준선이 생긴다.
6. 전체 흐름을 한 번에 보기

여기서 직접 만들 프로그램은 맨 아래, ffplay 자리에 들어간다. 이미 정상 동작하는 체인이 있으니, 이제 비교가 가능해진다.
7. 언제 특히 유용한가?
이 환경은 생각보다 많은 상황에서 도움이 된다.
- C++로 RTSP 클라이언트를 직접 구현할 때
- live555 기반 클라이언트를 테스트할 때
- RTP 패킷을 직접 분석하고 싶을 때
- TCP와 UDP 차이를 비교할 때
- Digest 인증을 구현할 때
뭔가를 새로 붙일 때마다, 기존 환경과 비교해보면 감이 훨씬 빨리 온다.
8. 몇 가지 주의할 점
TCP vs UDP
TCP는 안정적이다. 대신 약간 느릴 수 있다.
UDP는 빠르지만 패킷 손실 가능성이 있다.
처음 구현할 때는 TCP가 편하다. 변수 하나라도 줄이는 게 낫다.
재인코딩 문제
-c:v copy를 빼면 FFmpeg가 재인코딩을 한다. CPU 사용량이 올라가고, 지연이 생길 수 있다. 괜히 복잡도를 높이지 않는 편이 좋다.
네트워크 이슈
로컬에서는 잘 되는데 원격에서는 안 되는 경우가 있다. 대부분 방화벽이나 NAT 문제다. 이건 코드가 아니라 네트워크 설정 문제다. 경험상, 여기서 시간을 많이 쓴다.
마무리하며
RTSP 클라이언트를 만들기 전에 서버 환경부터 준비하는 것. 처음에는 돌아가는 길처럼 느껴질 수 있다. 그런데 막상 해보면, 이게 제일 빠르다.
테스트 가능한 기준 환경을 먼저 만들고, 그 위에 코드를 얹는다. 이 순서를 지키는 것만으로도 디버깅 난이도가 확연히 낮아진다.
조급해지기 쉽지만, 한 번만 제대로 세팅해두면 이후 작업이 훨씬 수월해진다. 나도 몇 번 시행착오를 겪고 나서야 이 순서의 중요성을 체감했다.
'잡(job)기술' 카테고리의 다른 글
| RTSP 응답을 읽는다는 것 - 왜 recv() 한 번으로 끝내면 안 되는가 (3) | 2026.02.25 |
|---|---|
| RTSP 클라이언트 첫걸음: TCP 연결하고 OPTIONS 한 번 던져보기 (0) | 2026.02.21 |
| SVN Merge 실수 줄이는 법: 체리픽부터 mergeinfo까지 실전 전략 5가지 (0) | 2026.02.11 |
| NestJS 모듈의 비밀: 왜 클래스 안은 텅 비어 있을까? (0) | 2026.02.06 |
| 데이터베이스 성능의 핵심, 복합 인덱스를 이해하는 4가지 포인트 (1) | 2026.02.04 |