블로그 목록
Media12분 읽기

Stream과 RTP의 관계 — Cloud Recording 디버깅에서 헷갈리지 않는 법

stream은 논리적인 미디어 흐름이고 RTP는 그 미디어를 네트워크로 운반하는 패킷 프로토콜입니다. 하지만 WebRTC API의 MediaStream, Agora 운영 문맥의 stream, RTP의 SSRC packet stream은 같은 말이 아닙니다. 이 글은 track, stream, RTP, RTCP의 층위를 분리하고, muteLocalVideoStream, enableLocalVideo, stopPreview, leaveChannel이 Cloud Recording에서 어떻게 다르게 보이는지 검증된 기준으로 정리합니다.

WebRTCAgoraCloud RecordingRTPRTCPMediaStreamMediaStreamTrackSSRCMuteRecording

WebRTC나 Agora Cloud Recording을 보다 보면 track, stream, RTP, RTCP가 한꺼번에 튀어나옵니다. 다 미디어와 관련된 말이라 비슷해 보이지만, 실제로는 서로 다른 층의 개념입니다.

결론부터 말하면 이렇습니다.

서비스 문맥의 stream은 "사용자가 보내는 논리적인 미디어 흐름"이고, RTP는 그 미디어를 네트워크로 운반하는 패킷 프로토콜입니다.

다만 여기서 조심해야 할 점이 하나 있습니다. WebRTC 표준 API에서 MediaStream은 track을 묶는 객체이고, 실제 송수신의 중심은 MediaStreamTrackRTCRtpSender/RTCRtpReceiver입니다. 반면 현장에서 "Agora stream", "host stream", "video stream"이라고 말할 때는 제품/운영 문맥의 논리적인 송출 흐름을 뜻하는 경우가 많습니다.

이 둘을 섞으면 Cloud Recording idle, mute, host 퇴장 같은 이슈를 잘못 해석하게 됩니다.


1. 먼저 큰 그림

일반적인 실시간 미디어 흐름은 이렇게 볼 수 있습니다.

Camera / Mic
  -> MediaStreamTrack
  -> WebRTC sender
  -> RTP packets
  -> Network
  -> WebRTC receiver / Agora recorder
  -> MediaStreamTrack
  -> Playback / Recording file

표로 정리하면 더 선명합니다.

개념계층의미
MediaStreamTrack브라우저/API하나의 오디오 또는 비디오 소스
MediaStream브라우저/APItrack들을 묶는 그룹
Agora stream제품/운영특정 UID가 publish한 오디오/비디오 흐름
RTP packet네트워크 전송encoded audio/video를 잘게 나눈 패킷
RTCP제어/통계수신 품질, packet loss, jitter, sender/receiver report

한 줄 비유로는 이렇게 설명할 수 있습니다.

Track  = 카메라 또는 마이크 한 대
Stream = 방송 송출 한 줄기
RTP    = 그 방송을 네트워크로 나눠 보내는 택배 상자
RTCP   = 택배가 잘 도착하는지 알려주는 배송 리포트

2. WebRTC에서 정확한 단위는 track이다

브라우저 WebRTC에서 카메라와 마이크를 가져오면 보통 MediaStream 안에 MediaStreamTrack들이 들어 있습니다.

const stream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true,
});

for (const track of stream.getTracks()) {
  peerConnection.addTrack(track, stream);
}

여기서 중요한 건 addTrack()입니다. WebRTC의 전송 단위는 stream 전체가 아니라 track입니다.

MDN도 MediaStream을 여러 track으로 구성된 미디어 콘텐츠 stream이라고 설명하고, addTrack()은 peer connection에 전송할 media track을 추가한다고 설명합니다. 또한 RTCRtpSender는 특정 MediaStreamTrack이 어떻게 인코딩되고 remote peer로 전송되는지 제어하는 객체입니다.

즉 표준 API 관점에서는 이렇게 말하는 게 가장 정확합니다.

MediaStream은 track 묶음이다.
전송되는 것은 track이다.
RTP sender/receiver는 track 단위 전송과 수신을 관리한다.

그런데 Agora나 RTC 운영 문맥에서는 여전히 "stream"이라는 말을 많이 씁니다.

예를 들어:

host의 video stream
remote user의 audio stream
recorder가 subscribe할 stream

이때의 stream은 브라우저 객체 MediaStream만을 뜻하지 않습니다. 보통은 "어떤 UID가 채널에 publish한 미디어 흐름"이라는 제품 레벨 표현입니다.


3. RTP에서 stream은 SSRC로 식별되는 패킷 흐름에 가깝다

RTP 쪽으로 내려가면 stream이라는 말은 다시 더 낮은 의미가 됩니다.

RFC 3550은 RTP를 실시간 오디오/비디오 같은 데이터를 전송하기 위한 프로토콜로 정의합니다. RTP 패킷에는 payload type, sequence number, timestamp 같은 정보가 들어가고, 수신자는 이 정보를 이용해 패킷 순서와 재생 타이밍을 복원합니다.

RTP에서 특히 중요한 식별자는 SSRC입니다.

SSRC = Synchronization Source

SSRC는 RTP 패킷 stream의 출처를 식별합니다. 예를 들어 한 사용자의 마이크에서 나온 오디오 RTP 패킷들은 같은 SSRC와 같은 timing/sequence number 공간에 묶일 수 있습니다.

그래서 RTP 레벨에서 더 정확한 표현은 이렇습니다.

RTP stream = 같은 SSRC/timing/sequence 공간을 공유하는 RTP packet 흐름

서비스 문맥의 "video stream"과 RTP 문맥의 "RTP stream"은 연결되어 있지만, 같은 말은 아닙니다.

Agora video stream
  -> 내부적으로 하나 이상의 RTP packet 흐름으로 운반됨

RTP packet
  -> 그 흐름을 네트워크로 보내는 실제 조각

4. RTCP는 media가 아니라 상태 리포트다

RTP가 미디어 데이터를 운반한다면, RTCP는 그 전송 상태를 관찰하고 조정하기 위한 control protocol입니다.

대표적으로 다음 정보가 RTCP 계층에서 다뤄집니다.

  • packet loss
  • jitter
  • 송신자/수신자 report
  • participant 식별 정보
  • BYE 같은 세션 종료 신호

그래서 디버깅할 때는 이렇게 나눠 봐야 합니다.

영상/음성이 실제로 들어오는가?      -> RTP/media path
품질 통계가 어떻게 보이는가?         -> RTCP/stats path
채널에 누가 있고 무엇을 publish했나? -> RTC signaling/product state

5. Agora에서 mute와 leave는 다른 문제다

Agora에서 영상이 안 보인다고 해서 모두 같은 상태는 아닙니다.

아래 네 가지는 결과 화면만 보면 비슷해 보일 수 있지만, 실제 위치가 다릅니다.

enableLocalVideo(false)
muteLocalVideoStream(true)
stopPreview()
leaveChannel()

enableLocalVideo(false)

카메라 캡처 또는 로컬 비디오 기능 자체를 끄는 쪽에 가깝습니다.

enableLocalVideo(false)
  -> camera capture 중단
  -> video frame 생성 중단
  -> publish할 video media가 없음
  -> video RTP media packet도 중단되거나 의미 있는 payload가 없음

muteLocalVideoStream(true)

로컬 video stream의 publish를 mute하는 쪽입니다.

muteLocalVideoStream(true)
  -> 사용자는 channel에 남아 있음
  -> video publish가 mute됨
  -> receiver/recorder가 받을 video media가 없음

stopPreview()

로컬 preview 렌더링을 멈추는 동작입니다.

stopPreview()
  -> 내 화면의 미리보기 중단
  -> publish 여부와는 별도

이 API를 "송출 중단"으로 이해하면 위험합니다. 로컬 화면에서 안 보이는 것과 네트워크로 안 나가는 것은 다른 층의 문제입니다.

leaveChannel()

채널에서 나가는 동작입니다.

leaveChannel()
  -> participant 자체가 channel에서 사라짐
  -> stream mute 문제가 아니라 presence 문제

Cloud Recording에서는 이 차이가 특히 중요합니다.


6. Cloud Recording은 RTP packet 하나하나가 아니라 채널 상태와 publish 상태를 본다

Agora Cloud Recording은 서버 사이드 recorder가 RTC channel에 들어가서 오디오/비디오를 녹화하는 구조입니다. 로컬 Agora 레퍼런스 기준으로 lifecycle은 다음 순서입니다.

acquire -> start -> query -> stop

그리고 전제는 active RTC channel과 participants입니다. 시작 시점에 사용자가 없으면 435 No users in channel 같은 에러가 발생할 수 있습니다.

여기서 흔한 오해가 나옵니다.

RTP packet이 없으면 Cloud Recording이 무조건 idle인가?

그렇게 단순하지 않습니다.

Cloud Recording 관점에서 먼저 봐야 하는 것은 보통 다음 세 가지입니다.

1. recorder bot이 같은 channel에 들어왔는가?
2. channel에 사용자가 있는가?
3. 그 사용자 중 audio/video를 publish하는 사람이 있는가?

Agora Support 문서 기준으로 Cloud Recording은 recording channel에 사용자가 없는 상태가 일정 시간 지속되면 자동으로 stop하고 leave할 수 있으며, 기본값은 30초입니다. 이 시간은 maxIdleTime으로 조정할 수 있습니다.

또 다른 Agora Support 문서는 Acquire/Start가 200 OK를 반환했더라도 녹화 파일이 생성되지 않을 수 있는 원인으로 "recorder가 join한 뒤 channel에서 아무도 audio/video stream을 publish하지 않은 경우"를 설명합니다.

따라서 Cloud Recording 디버깅은 이렇게 정리하는 편이 안전합니다.

상태해석
channel에 사용자가 없음recorder가 녹화할 participant 자체가 없음
사용자는 있지만 publish된 audio/video가 없음session은 시작됐지만 결과 파일이 없을 수 있음
host가 channel에 있지만 video만 muteaudio만 녹화되거나, video 없는 결과가 될 수 있음
host가 leaveChannel() 호출mute가 아니라 channel presence 종료
stopPreview()만 호출로컬 미리보기 문제일 수 있으므로 publish 상태를 별도로 확인해야 함

7. "RTP packet 없음"과 "녹화 idle"은 같은 말이 아니다

운영 로그에서 "media packet이 없다"는 말과 "recording이 idle이다"는 말을 같은 뜻으로 쓰면 사고가 납니다.

예를 들어 사용자가 채널에 남아 있지만 카메라와 마이크를 모두 mute한 상태를 생각해봅니다.

participant presence = 있음
published media      = 없음 또는 mute
RTP media packet     = 없음 또는 거의 없음
recording session    = 설정에 따라 유지될 수 있음
recording output     = 없거나 불완전할 수 있음

반대로 사용자가 아예 나간 경우는 다릅니다.

participant presence = 없음
published media      = 없음
RTP media packet     = 없음
recording session    = idle timeout 후 자동 종료 가능

둘 다 RTP packet은 없을 수 있지만, 원인은 다릅니다.

그래서 장애 분석에서는 "packet이 없다"에서 멈추면 안 됩니다. 반드시 한 단계 위의 상태를 같이 봐야 합니다.

RTP 없음
  -> publish가 mute된 건가?
  -> capture가 꺼진 건가?
  -> recorder가 subscribe를 못 한 건가?
  -> host가 channel에서 나간 건가?
  -> channelType이 앱과 recording 설정에서 일치하는가?

8. 실무 디버깅 순서

Cloud Recording에서 "녹화가 비었다" 또는 "idle로 끝났다"는 이슈를 보면 이 순서로 확인합니다.

1단계: channel presence

녹화 시작 시점에 channel에 사용자가 있었나?
Live Broadcasting이면 host 역할 사용자가 있었나?

이 단계는 RTP보다 위입니다. 사용자가 없으면 media packet도 있을 수 없습니다.

2단계: publish 상태

사용자가 audio/video track을 publish했나?
mute 상태였나?
audience role로 들어와 publish가 막힌 건 아닌가?

Agora live channel에서는 host와 audience 역할이 중요합니다. audience는 기본적으로 시청자이고, publish 주체는 host입니다.

3단계: recorder 설정

recordingConfig.channelType이 실제 channel profile과 맞나?
streamTypes가 기대한 media 종류와 맞나?
subscribeAudioUids / subscribeVideoUids 필터가 너무 좁지 않나?

channelType이 맞지 않거나 subscribe 대상이 잘못되면 사용자가 publish 중이어도 recorder가 기대한 stream을 못 받을 수 있습니다.

4단계: media packet과 stats

outbound-rtp / inbound-rtp stats가 증가하나?
packet loss, jitter, bitrate는 어떤가?

이 단계에서 RTP/RTCP 관찰이 의미가 있습니다. 즉 RTP는 가장 먼저 볼 대상이 아니라, presence와 publish 상태를 확인한 뒤 내려가서 보는 전송 계층 증거입니다.


9. 정리

stream과 RTP의 관계는 이렇게 정리하면 됩니다.

서비스 관점
  stream = 사용자가 publish한 논리적인 미디어 흐름

WebRTC API 관점
  MediaStream = MediaStreamTrack 묶음
  MediaStreamTrack = 실제 송수신되는 audio/video 단위
  RTCRtpSender/Receiver = track의 RTP 송수신을 관리

RTP 관점
  RTP packet = encoded media를 네트워크로 운반하는 조각
  RTP stream = SSRC/timing/sequence 공간으로 묶이는 packet 흐름
  RTCP = 품질과 제어 정보를 주고받는 보조 프로토콜

Cloud Recording 관점
  핵심 질문 = channel에 user/host가 있고, recorder가 구독할 published stream이 있는가?

가장 중요한 한 문장은 이것입니다.

RTP는 stream을 운반하는 네트워크 조각이고, Cloud Recording 디버깅은 RTP packet 단위가 아니라 channel presence, host role, publish/subscription 상태에서 시작해야 합니다.


검증한 기준

© 2026 Frank Kim. All rights reserved.