실시간 스트리밍 서비스 프로젝트
RTMP 서버 기반 실시간 스트리밍 시스템 구축기
실시간 방송, 어떻게 구현할까요?
요즘 라이브 스트리밍은 유튜브나 트위치만의 전유물이 아니잖아요? 팀 내부 프로젝트나 소규모 방송 플랫폼에서도 충분히 활용할 수 있어요. 그래서 이번에 우리가 만든 시스템은 복잡한 WebRTC 없이, 상대적으로 구성이 단순하면서도 확장성이 좋은 RTMP → HLS 기반 스트리밍 서버입니다.
목차
- 전체 아키텍처 개요
- RTMP 서버 구성: NGINX로 방송 받기
- FFmpeg로 RTMP → HLS 변환하기
- NestJS에서 HLS 스트리밍 제공
- 자주 묻는 질문 (FAQ)
- 구현 후 느낀 점과 개선 방향
- 핵심 기술 요약
전체 아키텍처 개요
전체 흐름은 다음과 같아요. 송출자가 RTMP로 방송을 전송 → NGINX RTMP 서버가 수신 → FFmpeg로 실시간 변환 → NestJS 서버에서 스트리밍 및 채팅 제공 → 사용자 시청!

RTMP 서버 구성: NGINX로 방송 받기
nginx-rtmp-module
을 사용해서 RTMP 스트림을 받았습니다. OBS에서 rtmp://서버주소/live
형식으로 전송하면 서버에서 받아줘요.
FFmpeg로 RTMP → HLS 변환하기
웹에서는 RTMP를 직접 재생하기 어렵기 때문에, HLS로 변환해주는 작업이 필요해요. 여기서 FFmpeg가 등장합니다.
변환 지연은 평균적으로 2~5초 이내라서, 사용자 경험에도 문제 없었어요.
- RTMP 스트림 수신
- FFmpeg로 m3u8/ts 생성
- 지정 폴더에 파일 저장
- NestJS에서 정적 파일로 제공
NestJS에서 HLS 스트리밍 제공
ServeStaticModule
을 이용해 HLS 파일들을 정적으로 서빙합니다. 브라우저에서는 /videos/output.m3u8
같은 주소로 접근하면 됩니다.
어떻게 테스트했는지?
OBS로 직접 송출 테스트
Video.js + hls.js 조합으로 스트리밍 브라우저에서 재생 확인
Postman으로 API 호출 테스트
실제로도 꽤 안정적으로 동작했고, 시청자 50명 이상 붙여도 문제 없었습니다
나중에 붙일만한 기능들
다중 비트레이트 출력 (480p, 720p 등)
CDN 연동 (CloudFront, Fastly 등)
스트림 키 만료 기능
녹화 파일 .mp4 자동 저장
관리자 페이지 대시보드
구현 후 느낀 점과 개선 방향
처음엔 막막했지만, 하나하나 연결되면서 동작할 때 정말 짜릿했어요. 아직 부족한 부분도 있죠. 예를 들면:
- 지연 최소화를 위한 세그먼트 설정
- CDN 연동 고려 (CloudFront 등)
- JWT 기반 인증 강화
자주 묻는 질문 (FAQ)
Q. 왜 WebRTC는 안 썼냐고?
WebRTC는 극저지연에 특화돼 있긴 하지만, 그만큼 구현 난이도가 꽤 높아요. NAT 우회, TURN 서버 세팅, 시그널링 등 손볼 게 많고, 일대다 방송을 제대로 하려면 추가적인 미디어 서버도 붙여야 하죠.
반면 HLS는 설정이 비교적 단순하면서도 웹 브라우저 호환성이 뛰어나고, 정적 파일 형태라 CDN이랑 연동하기도 쉬워요. 물론 5~10초 정도의 지연은 있지만, 대부분의 콘텐츠에는 이 정도 딜레이가 큰 문제가 되지 않더라고요.
Q. FFmpeg 실행 시 옵션은요?
- 스트리밍 특성상 저지연 + 안정성 + HLS 호환을 고려해 최적화된 옵션을 사용합니다.
ffmpeg -i rtmp://localhost/live/$name \ -c:v libx264 -preset veryfast -tune zerolatency \ -c:a aac -ar 44100 -ac 2 \ -f hls \ -hls_time 2 \ -hls_list_size 5 \ -hls_flags delete_segments+append_list \ -hls_segment_filename /hls/$name/segment_%03d.ts \ /hls/$name/index.m3u8
옵션 | 설명 |
---|---|
-preset veryfast |
인코딩 속도를 빠르게 하여 지연을 최소화합니다. |
-tune zerolatency |
실시간 스트리밍용으로 인코딩 버퍼링을 줄입니다. |
-c:v libx264 |
비디오 코덱을 H.264(x264)로 설정합니다. |
-c:a aac |
오디오 코덱을 AAC로 설정합니다. |
-ar 44100 |
오디오 샘플레이트를 44.1kHz로 설정합니다. |
-ac 2 |
오디오 채널을 스테레오(2채널)로 설정합니다. |
-f hls |
출력 포맷을 HLS(HTTP Live Streaming)로 지정합니다. |
-hls_time 2 |
각 .ts 세그먼트를 2초 간격으로 분할합니다. |
-hls_list_size 5 |
m3u8 재생 목록에 최신 세그먼트 5개만 유지합니다. |
-hls_flags delete_segments+append_list |
오래된 세그먼트를 자동 삭제하고 목록을 누적합니다. |
-hls_segment_filename |
세그먼트 파일의 이름 형식을 지정합니다 (예: segment_001.ts). |
📌 실시간성이 중요한 프로젝트일수록 preset은 빠르게, hls_time은 짧게 유지하는 게 핵심이에요.
단, 너무 짧게 자르면 오히려 버퍼링이 더 자주 발생할 수 있으니 2초~4초 선이 현실적입니다.
**Q. NestJS에서 정적 파일 보안은 어떻게 하나요?
NestJS의 기본 Static Serving은 무방비입니다. 예를 들어 아래처럼 설정할 경우:
app.useStaticAssets(join(__dirname, '..', 'public/hls'));
이렇게만 설정하면 누구든지 .m3u8 주소를 알기만 하면 직접 접근이 가능합니다. 즉, 인증 없이 영상 스트림을 가져갈 수 있게 되는 것이죠. 프록시 기반 전급제어로 정적 파일 보안합니다.
이를 막기 위해선 m3u8/ts 파일은 공개하지 않고, NestJS 내에서 /video/:streamKey 같은 경로로 프록시합니다.
이 프록시 경로에서 JWT나 세션 검사를 합니다.
@Get('/video/:key')
@UseGuards(AuthGuard)
streamVideo(@Param('key') key: string, @Res() res: Response) {
const filePath = `/hls/${key}/index.m3u8`;
res.sendFile(filePath, { root: join(__dirname, '..', 'public') });
}
Q. 시청자 증가 시 어떤 부분을 확장성 있나요?**
- HLS는 정적 파일 기반이라서
.m3u8
와.ts
파일을 CDN(예: CloudFront, Cloudflare 등)에 올리기만 해도 글로벌 캐싱이 가능합니다. - 서버에 직접 요청이 들어오는 것이 아니라 캐시된 파일을 제공하므로, 수천 명이 동시에 시청해도 서버 부하가 거의 없습니다.
- 정적 파일이기 때문에 로드 밸런싱 없이도 잘 분산됩니다.
💡 핵심 기술 요약
기술 | 정의 | 장점 | 단점 |
---|---|---|---|
RTMP | 송출자(OBS 등) → 서버 간 실시간 영상 전송에 사용되는 프로토콜 | - 저지연 전송 - 송출 프로그램에서 널리 지원 |
- 브라우저 미지원 - 자체 보안 기능 없음 |
NGINX RTMP 모듈 | NGINX에 RTMP 기능을 추가하여 스트림 수신/중계 역할 수행 | - 가볍고 구성 간단 - FFmpeg 자동 실행 연동 가능 |
- 인증 기능 미비 - 대규모 확장에는 제약 |
FFmpeg | RTMP 스트림을 HLS(m3u8+ts)로 변환하는 오픈소스 툴 | - 고성능 인코딩 - 다양한 포맷 지원 - 자동화에 유리 |
- CPU 사용량 높음 - 설정이 복잡할 수 있음 |
HLS | 세그먼트 기반 HTTP 스트리밍 방식으로 Apple이 개발, 웹/모바일 기본 지원 | - 웹 호환성 뛰어남 - CDN과 쉽게 연동 - 적응형 화질 가능 |
- 5~10초 지연 발생 - 초저지연 방송엔 부적합 |
.
태그: RTMP 서버, nginx-rtmp-module, 실시간 방송, FFmpeg, HLS 스트리밍, m3u8, ts 파일, NestJS, WebSocket 채팅, 라이브 시스템 구축