#. MediaMTX와 Node.js로 만든 Stream Control 서버 구성 정리

##. MediaMTX와 Node.js로 만든 Stream Control 사용자 화면

한 줄 요약
Stream Control 서버는 업로드한 영상 파일이나 외부 라이브 입력을 RTSP, RTMP, HLS, WebRTC, SRT 같은 여러 스트리밍 프로토콜로 송출하기 위한 컨트롤 서버입니다. 직접 미디어 서버를 구현하지 않고, 검증된 MediaMTX를 프로토콜 게이트웨이로 사용하고 Node.js API가 파일 관리, FFmpeg 송출 제어, 관리 화면을 담당하는 구조로 만들었습니다.
이 프로그램을 만든 이유
가볍게 사용할 수 있는 스트리밍 서버가 필요해서 이 프로그램을 만들었습니다. 복잡한 상용 미디어 서버를 도입하기 전에, 내부 테스트나 소규모 환경에서 빠르게 영상 업로드, 라이브 입력, 멀티 프로토콜 출력, 멀티캐스트 릴레이를 확인할 수 있는 구성이 필요했습니다.
그래서 핵심 미디어 처리는 MediaMTX와 FFmpeg에 맡기고, 실제 사용자가 필요한 업로드, 송출 시작/중지, 출력 URL 확인, 접속자 집계 같은 제어 기능만 별도의 Web UI와 API로 구성했습니다.
설치가 편리한 Docker 기반 구성
이 프로그램은 Docker에 설치하면 동작하도록 구성되어 있어 설치와 실행이 비교적 편리합니다. 서버에 Node.js, FFmpeg, MediaMTX를 각각 직접 설치하고 버전을 맞추는 대신, Docker Compose로 필요한 컨테이너를 한 번에 실행할 수 있습니다.
기본적으로 MediaMTX 컨테이너와 Control API 컨테이너가 함께 올라가며, 업로드 파일과 설정은 로컬 볼륨에 저장됩니다. 따라서 테스트 환경을 만들 때도 docker compose up -d --build 명령만으로 전체 스트리밍 서버 구성을 빠르게 띄울 수 있습니다.
왜 이런 구조로 만들었나
스트리밍 서버를 직접 구현하려면 프로토콜별 처리, 세션 관리, WebRTC 신호 교환, HLS 세그먼트 생성, SRT 연결 처리까지 모두 구현해야 합니다. 이 프로젝트에서는 그 복잡한 영역을 MediaMTX에 맡기고, 서비스에 필요한 제어 로직만 Node.js로 분리했습니다.
이 방식의 장점은 명확합니다.
- 미디어 프로토콜 처리는 MediaMTX가 담당합니다.
- 파일 업로드, 송출 시작/중지, URL 생성 같은 서비스 로직은 Express API가 담당합니다.
- FFmpeg는 업로드 파일을 실시간 스트림처럼 변환하거나 멀티캐스트로 릴레이하는 작업자 역할만 수행합니다.
- 전체 실행 환경은 Docker Compose로 묶어 Linux, macOS, Windows 개발 환경에서 같은 방식으로 실행할 수 있습니다.
전체 구성
구성은 크게 네 영역으로 나눌 수 있습니다.
| 영역 | 역할 |
|---|---|
| Web UI | 브라우저에서 파일 업로드, 송출 시작/중지, 출력 URL 확인, 멀티캐스트 릴레이 제어 |
| Node.js Control API | REST API, 업로드 파일 관리, FFmpeg 프로세스 제어, MediaMTX 상태 조회 |
| FFmpeg / ffprobe | 영상 파일 분석, 트랜스코딩, RTSP publish, UDP MPEG-TS 멀티캐스트 릴레이 |
| MediaMTX | RTSP, RTMP, HLS, WebRTC, SRT, MoQ 입출력을 처리하는 미디어 게이트웨이 |
업로드 기반 송출 흐름은 다음과 같습니다.
- 사용자가 Web UI 또는 API로 영상 파일을 업로드합니다.
- API 서버가 파일을
/data/uploads에 저장하고ffprobe로 재생 시간을 확인합니다. - 송출 버튼을 누르면 API 서버가 FFmpeg 프로세스를 실행합니다.
- FFmpeg가 파일을 실시간 속도로 읽어 MediaMTX에 RTSP로 publish합니다.
- MediaMTX가 같은 스트림을 RTSP, RTMP, Low-Latency HLS, WebRTC/WHEP, SRT 등으로 재배포합니다.
라이브 입력은 더 단순합니다. 카메라, 인코더, 외부 FFmpeg가 MediaMTX로 직접 publish하면, MediaMTX가 출력 프로토콜로 재배포합니다. 이때 API 서버는 입력 URL과 출력 URL을 생성하고, MediaMTX API를 통해 현재 path와 접속자 정보를 조회합니다.
제작에 사용한 언어와 라이브러리
| 구분 | 사용 기술 | 용도 |
|---|---|---|
| 런타임 | Node.js 22 | Control API 실행 |
| 언어 | JavaScript, CommonJS | API 서버와 브라우저 UI 구현 |
| API 서버 | Express 4 | REST API, 정적 파일 제공 |
| 파일 업로드 | Multer 2 | multipart 업로드 처리 |
| 미디어 서버 | MediaMTX | RTSP, RTMP, HLS, WebRTC, SRT, MoQ 게이트웨이 |
| 미디어 처리 | FFmpeg | 트랜스코딩, RTSP publish, 멀티캐스트 릴레이 |
| 미디어 분석 | ffprobe | 업로드 파일의 duration 추출 |
| 프론트엔드 | HTML, CSS, Vanilla JavaScript | 별도 프레임워크 없는 관리 화면 |
| WebRTC 재생 | WHEP + RTCPeerConnection | 브라우저 WebRTC 플레이어 |
| 컨테이너 | Docker, Docker Compose | API 서버와 MediaMTX 실행 환경 구성 |
| 저장소 | 로컬 볼륨, JSON 카탈로그 | 업로드 파일과 메타데이터 저장 |
현재 API 서버의 핵심 의존성은 express와 multer입니다. 미디어 프로토콜 구현은 애플리케이션 코드에 직접 넣지 않고 MediaMTX와 FFmpeg에 맡겼기 때문에 Node.js 코드의 책임이 비교적 단순합니다.
Docker 구성
Docker Compose는 두 개의 서비스를 실행합니다.
| 서비스 | 이미지/빌드 | 역할 |
|---|---|---|
mediamtx |
bluenviron/mediamtx:latest |
미디어 게이트웨이 |
api |
./api/Dockerfile |
Node.js Control API, Web UI, FFmpeg 실행 |
API 컨테이너는 node:22-bookworm-slim을 기반으로 만들고, 내부에 ffmpeg, ca-certificates, tini를 설치합니다.
FROM node:22-bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg ca-certificates tini \
&& rm -rf /var/lib/apt/lists/*
API 컨테이너 안에 FFmpeg를 같이 넣은 이유는 송출 작업의 시작/중지 수명주기를 API 서버가 직접 관리하기 위해서입니다. 사용자가 Web UI에서 "송출 시작"을 누르면 API 서버가 FFmpeg 프로세스를 실행하고, "중지"를 누르면 해당 프로세스를 종료합니다.
공유 볼륨은 다음처럼 구성했습니다.
| 경로 | 설명 |
|---|---|
./data:/data |
업로드 파일, 카탈로그, 녹화 데이터의 기본 저장 위치 |
./data/uploads |
업로드된 원본 영상 파일 |
./data/catalog/assets.json |
업로드 파일 메타데이터 |
./mediamtx/mediamtx.yml:/mediamtx.yml:ro |
MediaMTX 설정 파일 |
./data/recordings:/recordings |
MediaMTX 녹화 저장 경로 |
노출 포트
기본 포트는 다음과 같습니다.
| 기능 | 포트/URL |
|---|---|
| Web UI / Control API | http://localhost:3001 |
| RTMP | rtmp://localhost:1935/{streamName} |
| RTSP | rtsp://localhost:8554/{streamName} |
| HLS | http://localhost:8888/{streamName}/index.m3u8 |
| WebRTC Player | http://localhost:3001/player.html?stream={streamName} |
| WHEP endpoint | http://localhost:8889/{streamName}/whep |
| SRT | srt://localhost:8890?streamid=read:{streamName} |
| UDP MPEG-TS ingest | udp://localhost:1234?pkt_size=1316 |
| MediaMTX Metrics | http://localhost:9998 |
운영 서버에서는 .env로 공개 호스트와 API 포트를 지정합니다.
API_PUBLIC_PORT=3001
PUBLIC_HOST=stream.example.com
# 리버스 프록시나 HTTPS 경로를 별도로 쓰는 경우만 지정
# PUBLIC_API_BASE_URL=https://stream.example.com/api
실행 명령은 다음과 같습니다.
docker compose up -d --build
Docker Compose v1 환경에서는 다음 명령을 사용할 수 있습니다.
docker-compose up -d --build
주요 API
파일 업로드:
curl -F "file=@sample.mp4" "http://localhost:3001/v1/assets"
업로드 파일 송출 시작:
curl -X POST "http://localhost:3001/v1/assets/{assetId}/stream" \
-H "Content-Type: application/json" \
-d '{"loop":true,"profile":"broad"}'
송출 중지:
curl -X DELETE "http://localhost:3001/v1/assets/{assetId}/stream"
라이브 입력과 출력 URL 조회:
curl "http://localhost:3001/v1/ingest/live_test"
멀티캐스트 릴레이 시작:
curl -X POST "http://localhost:3001/v1/multicast" \
-H "Content-Type: application/json" \
-d '{"sourceStreamName":"live_test","address":"239.10.10.10","port":5000,"ttl":16}'
송출 프로파일
업로드 파일을 라이브 스트림처럼 송출할 때는 두 가지 프로파일을 제공합니다.
| UI 이름 | API profile | 변환 코덱 | 권장 대상 |
|---|---|---|---|
| 호환 송출 | broad |
H.264 + AAC | VLC, RTSP, RTMP, HLS, SRT |
| WebRTC 송출 | webrtc |
H.264 + Opus | 브라우저 WebRTC/WHEP 재생 |
일반적인 플레이어 테스트는 호환 송출이 무난합니다. WebRTC 브라우저 재생에서 오디오 호환성이 중요하면 WebRTC 송출을 사용합니다. 반대로 WebRTC용 Opus 오디오는 RTMP나 일부 HLS 플레이어에서 호환성이 떨어질 수 있으므로 목적에 따라 프로파일을 나누었습니다.
접속자 집계
API 서버는 MediaMTX의 Control API를 호출해서 프로토콜별 read 세션을 집계합니다.
- RTSP 세션
- RTMP 연결
- HLS 세션
- WebRTC 세션
- SRT 연결
파일을 MediaMTX로 publish하는 FFmpeg 송출자는 시청자로 보지 않고, read 세션만 접속자로 계산합니다. HLS는 HTTP 세그먼트 기반 특성 때문에 접속 수가 짧게 변할 수 있습니다.
멀티캐스트 릴레이
멀티캐스트 송출은 API 서버가 별도의 FFmpeg 프로세스를 실행하는 방식입니다.
MediaMTX RTSP stream -> FFmpeg read -> UDP MPEG-TS multicast
예를 들어 live_test 스트림을 239.10.10.10:5000으로 송출하면, 내부적으로 FFmpeg가 다음과 같은 흐름으로 동작합니다.
rtsp://mediamtx:8554/live_test
-> udp://239.10.10.10:5000?pkt_size=1316&ttl=16
이 릴레이는 기본적으로 remux/copy를 우선하므로, 소스 코덱이 MPEG-TS 수신 장비와 맞지 않으면 실패할 수 있습니다.
운영 시 고려할 점
현재 구성은 MVP와 개발 환경에 가깝습니다. 운영 환경에서는 다음 항목을 추가로 검토해야 합니다.
- 인증: 현재 MediaMTX 설정은 개발 편의를 위해 publish/read 인증이 열려 있습니다.
- HTTPS: 브라우저 WebRTC, 도메인 배포, 리버스 프록시 환경에서는 HTTPS 구성이 필요합니다.
- WebRTC NAT: 외부망에서 WebRTC를 안정적으로 쓰려면
webrtcAdditionalHosts, STUN/TURN 구성이 필요합니다. - 인코딩 성능: FFmpeg 트랜스코딩은 CPU를 많이 사용하므로 동시 송출 수가 늘면 NVENC, QuickSync, VAAPI 같은 하드웨어 인코딩을 검토해야 합니다.
- 저장소: 업로드 파일이 늘어나는 운영 환경에서는 별도 디스크, NAS, S3 같은 외부 저장소 구성이 필요합니다.
- 이미지 태그 고정: 운영에서는
bluenviron/mediamtx:latest대신 검증한 버전 태그로 고정하는 것이 좋습니다. - 방화벽: RTSP, RTMP, HLS, WebRTC, SRT, UDP 포트가 실제 네트워크에서 열려 있는지 확인해야 합니다.
마무리
이 프로젝트의 핵심은 미디어 서버를 직접 구현하지 않고, MediaMTX와 FFmpeg를 조합해 빠르게 멀티 프로토콜 스트리밍 제어 서버를 만드는 것입니다. Node.js API는 서비스에 필요한 제어와 상태 관리에 집중하고, 실제 미디어 입출력은 검증된 도구에 맡겼습니다.
덕분에 업로드 파일을 즉시 라이브 스트림처럼 송출하거나, 외부 카메라 입력을 여러 프로토콜로 재배포하거나, 특정 스트림을 UDP MPEG-TS 멀티캐스트로 릴레이하는 기능을 비교적 단순한 구조로 구성할 수 있었습니다.
테스트용으로 사용을 희망하시는 분은 댓글 또는 인스타 DM으로 연락을 주시기 바랍니다.
'소프트웨어개발' 카테고리의 다른 글
| RTSP CCTV 실시간 관제 + AI 분석 프로그램 (1) | 2026.06.23 |
|---|