Posts RTSP 서버 구축과 카메라 스트림 방식 비교
Post
Cancel

RTSP 서버 구축과 카메라 스트림 방식 비교

카메라 스트림을 어떤 방식으로 보낼 것인가

카메라 여러 대를 붙여 추론 서버로 보내는 구성을 만들 때마다 같은 질문으로 돌아온다. 어떤 전송 방식을 쓸 것인가. 선택지는 크게 셋인데, 각각 잃는 것이 다르다.

방식지연손실다중 수신쓰는 곳
RTP over UDP가장 낮음 (수십 ms)패킷 손실 시 프레임 깨짐멀티캐스트 가능같은 LAN, 지연이 중요한 실시간 추론
RTSP (TCP 인터리브)중간 (100~300ms)손실 없음, 대신 밀림서버가 세션마다 복제일반 IP 카메라, 녹화
HLS / DASH높음 (수 초)없음무제한브라우저 배포, 모니터링

추론 파이프라인에서는 대개 앞의 둘 중 하나다. 판단 기준은 단순하다. 프레임이 한 장 깨지는 것과 300ms 늦는 것 중 어느 쪽이 더 나쁜가. 객체 검출은 깨진 프레임 한 장을 그냥 버리면 되지만, 300ms 밀린 좌표는 움직이는 물체에서는 그냥 틀린 값이 된다.

RTP over UDP — 가장 단순한 전송

송신 쪽에서 인코딩해 UDP로 던지고, 수신 쪽에서 받아 디코딩한다. 세션 협상이 없으니 서버 프로세스도 필요 없다.

송신:

1
2
3
4
5
6
7
gst-launch-1.0 -v \
  v4l2src device=/dev/video0 ! \
  video/x-raw,width=1920,height=1080,framerate=30/1 ! \
  videoconvert ! \
  x264enc tune=zerolatency bitrate=4000 speed-preset=ultrafast key-int-max=30 ! \
  rtph264pay config-interval=1 pt=96 ! \
  udpsink host=192.168.1.50 port=5000

수신:

1
2
3
4
gst-launch-1.0 -v \
  udpsrc port=5000 caps="application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=(string)H264,payload=(int)96" ! \
  rtph264depay ! h264parse ! avdec_h264 ! \
  videoconvert ! autovideosink sync=false

key-int-max=30은 1초에 한 번 키프레임을 강제하는 설정이다. 이걸 빼면 수신 쪽이 중간에 붙었을 때 첫 키프레임이 올 때까지 화면이 안 나오는데, 카메라 16대를 순차로 붙이는 상황에서 마지막 카메라가 몇 초씩 검은 화면으로 남는다. 대신 키프레임이 잦으면 비트레이트가 올라가므로 1초가 대체로 타협점이었다.

sync=false도 실용적으로 중요하다. 기본값에서는 싱크가 타임스탬프를 보고 재생 시각을 맞추는데, 송수신 클럭이 조금만 어긋나도 버퍼가 쌓이면서 지연이 계속 늘어난다. 실시간 추론에서는 “가장 최근 프레임”만 있으면 되므로 동기화를 꺼서 도착 즉시 처리한다.

RTSP 서버 — 여러 소비자에게 하나의 소스를 나눠줄 때

UDP 직송은 수신자를 미리 알아야 한다. 소비자가 늘거나 붙었다 떨어졌다 하면 RTSP 서버를 하나 두는 편이 낫다. gst-rtsp-server를 쓰면 파이프라인 문자열 하나로 서버가 된다.

1
sudo apt install -y libgstrtspserver-1.0-dev gstreamer1.0-rtsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstRtspServer', '1.0')
from gi.repository import Gst, GstRtspServer, GLib

Gst.init(None)

PIPELINE = (
    "( v4l2src device=/dev/video0 ! "
    "video/x-raw,width=1280,height=720,framerate=30/1 ! "
    "videoconvert ! x264enc tune=zerolatency bitrate=2500 speed-preset=veryfast ! "
    "rtph264pay name=pay0 pt=96 )"
)

server = GstRtspServer.RTSPServer()
server.set_service("8554")

factory = GstRtspServer.RTSPMediaFactory()
factory.set_launch(PIPELINE)
factory.set_shared(True)          # 접속자마다 카메라를 새로 열지 않는다

server.get_mount_points().add_factory("/cam0", factory)
server.attach(None)

GLib.MainLoop().run()

set_shared(True)가 핵심이다. 기본값(False)에서는 클라이언트가 접속할 때마다 파이프라인을 새로 만드는데, 소스가 /dev/video0처럼 단독 점유 장치면 두 번째 접속에서 바로 실패한다. 공유로 두면 하나의 파이프라인 출력을 세션 수만큼 복제한다.

접속:

1
2
gst-launch-1.0 rtspsrc location=rtsp://192.168.1.10:8554/cam0 latency=100 ! \
  rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! autovideosink sync=false

latency는 지터 버퍼 크기다. 0으로 두면 지연이 최소가 되지만 네트워크가 조금만 흔들려도 프레임이 깨진다. 유선 LAN이면 100ms 안팎, 무선이면 200ms 이상이 안정적이었다.

카메라 여러 대를 스크립트로 관리하기

카메라가 열 대를 넘어가면 파이프라인을 손으로 띄우는 것이 불가능해진다. 카메라별 셸 스크립트를 하나씩 만들어두고 포트만 다르게 두는 방식이 결국 가장 오래 버텼다.

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
# camera01.sh
DEV=/dev/video0
PORT=5001
HOST=192.168.1.50

gst-launch-1.0 -q \
  v4l2src device=${DEV} ! \
  video/x-raw,width=1280,height=720,framerate=30/1 ! \
  videoconvert ! x264enc tune=zerolatency bitrate=2500 speed-preset=ultrafast key-int-max=30 ! \
  rtph264pay config-interval=1 pt=96 ! \
  udpsink host=${HOST} port=${PORT}

포트를 카메라 번호와 맞춰두면(camera01 → 5001) 수신 쪽 설정 파일이 사람이 읽을 수 있는 형태가 된다. 사소해 보이지만 카메라 16대에서 한 대가 안 들어올 때, 포트 번호만 보고 어느 장비인지 즉시 아는 것과 표를 뒤지는 것은 장애 대응 시간이 완전히 다르다.

확인할 때 쓰는 것들

카메라가 실제로 무엇을 낼 수 있는지부터 확인한다.

1
2
v4l2-ctl --list-devices
v4l2-ctl -d /dev/video0 --list-formats-ext

--list-formats-ext가 중요하다. 1080p 30fps를 요구했는데 카메라가 그 조합에서는 MJPEG만 지원하고 YUYV로는 5fps까지만 되는 경우가 흔하다. 그러면 파이프라인이 협상에 실패하면서 not-negotiated 오류가 나는데, 메시지만 보면 원인이 전혀 드러나지 않는다.

패킷이 실제로 나가는지는 tcpdump로 본다.

1
sudo tcpdump -i any -n udp port 5001 -c 20

파이프라인이 정상으로 보이는데 화면이 안 나오면 대개 방화벽이거나 멀티캐스트 라우팅이지, 코덱 문제가 아니다.

This post is licensed under CC BY 4.0 by the author.

Symbolic Link in ubuntu

임베디드 보드 추론 벤치마크에서 실제로 재야 하는 것