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에서 어떻게 다르게 보이는지 검증된 기준으로 정리합니다.
목차(18개 항목)
WebRTC나 Agora Cloud Recording을 보다 보면 track, stream, RTP, RTCP가 한꺼번에 튀어나옵니다. 다 미디어와 관련된 말이라 비슷해 보이지만, 실제로는 서로 다른 층의 개념입니다.
결론부터 말하면 이렇습니다.
서비스 문맥의 stream은 "사용자가 보내는 논리적인 미디어 흐름"이고, RTP는 그 미디어를 네트워크로 운반하는 패킷 프로토콜입니다.
다만 여기서 조심해야 할 점이 하나 있습니다. WebRTC 표준 API에서 MediaStream은 track을 묶는 객체이고, 실제 송수신의 중심은 MediaStreamTrack과 RTCRtpSender/RTCRtpReceiver입니다. 반면 현장에서 "Agora stream", "host stream", "video stream"이라고 말할 때는 제품/운영 문맥의 논리적인 송출 흐름을 뜻하는 경우가 많습니다.
이 둘을 섞으면 Cloud Recording idle, mute, host 퇴장 같은 이슈를 잘못 해석하게 됩니다.
1. 먼저 큰 그림
일반적인 실시간 미디어 흐름은 이렇게 볼 수 있습니다.
표로 정리하면 더 선명합니다.
| 개념 | 계층 | 의미 |
|---|---|---|
MediaStreamTrack | 브라우저/API | 하나의 오디오 또는 비디오 소스 |
MediaStream | 브라우저/API | track들을 묶는 그룹 |
| Agora stream | 제품/운영 | 특정 UID가 publish한 오디오/비디오 흐름 |
| RTP packet | 네트워크 전송 | encoded audio/video를 잘게 나눈 패킷 |
| RTCP | 제어/통계 | 송수신 보고, 동기화, 소스 식별, 세션 제어와 품질 피드백 |
한 줄 비유로는 이렇게 설명할 수 있습니다.
2. WebRTC에서 정확한 단위는 track이다
브라우저 WebRTC에서 카메라와 마이크를 가져오면 보통 MediaStream 안에 MediaStreamTrack들이 들어 있습니다.
여기서 중요한 건 addTrack()입니다. WebRTC의 전송 단위는 stream 전체가 아니라 track입니다.
MDN도 MediaStream을 여러 track으로 구성된 미디어 콘텐츠 stream이라고 설명하고, addTrack()은 peer connection에 전송할 media track을 추가한다고 설명합니다. 또한 RTCRtpSender는 특정 MediaStreamTrack이 어떻게 인코딩되고 remote peer로 전송되는지 제어하는 객체입니다.
즉 표준 API 관점에서는 이렇게 말하는 게 가장 정확합니다.
그런데 Agora나 RTC 운영 문맥에서는 여전히 "stream"이라는 말을 많이 씁니다.
예를 들어:
이때의 stream은 브라우저 객체 MediaStream만을 뜻하지 않습니다. 보통은 "어떤 UID가 채널에 publish한 미디어 흐름"이라는 제품 레벨 표현입니다.
3. RTP에서 stream은 SSRC로 식별되는 패킷 흐름에 가깝다
RTP 쪽으로 내려가면 stream이라는 말은 다시 더 낮은 의미가 됩니다.
RFC 3550은 RTP를 실시간 오디오/비디오 같은 데이터를 전송하기 위한 프로토콜로 정의합니다. RTP 패킷에는 payload type, sequence number, timestamp 같은 정보가 들어가고, 수신자는 이 정보를 이용해 패킷 순서와 재생 타이밍을 복원합니다.
RTP에서 특히 중요한 식별자는 SSRC입니다.
SSRC는 RTP 세션 안의 synchronization source를 식별합니다. 한 오디오 소스의 RTP 패킷은 일반적으로 한 SSRC와 해당 sequence/timestamp 공간에 묶이지만 SSRC는 충돌·재시작 등으로 바뀔 수 있습니다. 또한 하나의 WebRTC track이 simulcast나 RTX 구성에서 여러 SSRC의 RTP 흐름과 연결될 수 있습니다.
그래서 RTP 레벨에서 더 정확한 표현은 이렇습니다.
서비스 문맥의 "video stream"과 RTP 문맥의 "RTP stream"은 연결되어 있지만, 같은 말은 아닙니다.
4. RTCP는 미디어 데이터와 별도의 제어 프로토콜이다
RTP가 미디어 데이터를 운반한다면, RTCP는 송수신 품질 보고뿐 아니라 참가자 식별, 미디어 간 동기화와 세션 제어를 지원하는 control protocol입니다.
대표적으로 다음 정보가 RTCP 계층에서 다뤄집니다.
- packet loss
- jitter
- 송신자/수신자 report
- participant 식별 정보
- BYE 같은 세션 종료 신호
그래서 디버깅할 때는 이렇게 나눠 봐야 합니다.
5. Agora Android Video SDK 4.x에서 mute와 leave는 다르다
Agora에서 영상이 안 보인다고 해서 모두 같은 상태는 아닙니다.
아래 네 가지는 결과 화면만 보면 비슷해 보일 수 있지만, 실제 위치가 다릅니다.
enableLocalVideo(false)
현재 Android Video SDK 4.x 문서에서 이 호출은 로컬 비디오 캡처와 게시를 중단합니다.
muteLocalVideoStream(true)
로컬 video stream의 게시를 중단하지만 카메라 캡처 장치는 계속 동작합니다.
stopPreview()
로컬 preview 렌더링을 멈추는 동작입니다. 현재 Android 4.x 문서는 이 API의 호출 시점을 채널 참가 전 또는 퇴장 후로 제한해 설명합니다. 다른 플랫폼·SDK 버전은 계약이 다를 수 있으므로 해당 API 문서를 확인해야 합니다.
이 API를 "송출 중단"으로 이해하면 위험합니다. 로컬 화면에서 안 보이는 것과 네트워크로 안 나가는 것은 다른 층의 문제입니다.
leaveChannel()
채널에서 비동기로 나가고 SDK가 해당 채널 세션의 리소스를 정리하도록 요청하는 동작입니다.
Cloud Recording에서는 이 차이가 특히 중요합니다.
6. Cloud Recording은 채널·참가·게시·구독 상태를 함께 본다
Agora Cloud Recording은 서버 사이드 recorder가 RTC channel에 들어가서 오디오/비디오를 녹화하는 구조입니다. 로컬 Agora 레퍼런스 기준으로 lifecycle은 다음 순서입니다.
그리고 전제는 recorder가 올바른 RTC 채널에 참가해 대상 미디어를 구독할 수 있다는 것입니다. Acquire/Start가 200을 반환해도 채널에 게시된 오디오·비디오가 없으면 녹화 결과가 생성되지 않을 수 있습니다. Agora의 현재 지원 문서는 이 사례의 내부 오류 코드로 206을 제시하지만, 내부 코드를 모든 실패 원인에 일반화해서는 안 됩니다.
여기서 흔한 오해가 나옵니다.
그렇게 단순하지 않습니다.
Cloud Recording 관점에서 먼저 봐야 하는 것은 보통 다음 세 가지입니다.
Agora Support 문서 기준으로 Cloud Recording은 recording channel에 사용자가 없는 상태가 maxIdleTime 동안 이어지면 자동으로 stop하고 leave할 수 있으며 기본값은 30초입니다. 정확한 허용 범위와 적용 조건은 사용 중인 Cloud Recording API 버전과 설정 문서를 함께 확인해야 합니다.
또 다른 Agora Support 문서는 Acquire/Start가 200 OK를 반환했더라도 recorder가 참가한 뒤 아무도 오디오·비디오 stream을 게시하지 않으면 녹화 파일이 생성되지 않을 수 있다고 설명합니다.
따라서 Cloud Recording 디버깅은 이렇게 정리하는 편이 안전합니다.
| 상태 | 해석 |
|---|---|
| channel에 사용자가 없음 | recorder가 녹화할 participant 자체가 없음 |
| 사용자는 있지만 publish된 audio/video가 없음 | session은 시작됐지만 결과 파일이 없을 수 있음 |
| host가 channel에 있지만 video만 mute | audio만 녹화되거나, video 없는 결과가 될 수 있음 |
host가 leaveChannel() 호출 | mute가 아니라 channel presence 종료 |
stopPreview()만 호출 | 로컬 미리보기 문제일 수 있으므로 publish 상태를 별도로 확인해야 함 |
7. "RTP packet 없음"과 "녹화 idle"은 같은 말이 아니다
운영 로그에서 "media packet이 없다"는 말과 "recording이 idle이다"는 말을 같은 뜻으로 쓰면 사고가 납니다.
예를 들어 사용자가 채널에 남아 있지만 카메라와 마이크를 모두 mute한 상태를 생각해봅니다.
반대로 사용자가 아예 나간 경우는 다릅니다.
둘 다 RTP packet은 없을 수 있지만, 원인은 다릅니다.
그래서 장애 분석에서는 "packet이 없다"에서 멈추면 안 됩니다. 반드시 한 단계 위의 상태를 같이 봐야 합니다.
8. 실무 디버깅 순서
Cloud Recording에서 "녹화가 비었다" 또는 "idle로 끝났다"는 이슈를 보면 이 순서로 확인합니다.
1단계: channel presence
이 단계는 RTP보다 위입니다. 사용자가 없으면 녹화 대상 사용자의 미디어도 없습니다. recorder 자신의 제어 트래픽까지 없다고 단정하는 표현은 피합니다.
2단계: publish 상태
Agora Live Broadcasting 프로필에서는 host와 audience 역할이 게시 권한에 영향을 줍니다. 기본 역할과 전환 API는 플랫폼과 SDK 버전별 문서를 확인해야 합니다.
3단계: recorder 설정
channelType이 맞지 않거나 subscribe 대상이 잘못되면 사용자가 publish 중이어도 recorder가 기대한 stream을 못 받을 수 있습니다.
4단계: media packet과 stats
outbound-rtp와 inbound-rtp는 브라우저 WebRTC getStats() 보고서 유형입니다. Agora 네이티브 SDK와 Cloud Recording에서는 동일한 이름의 브라우저 통계가 항상 제공되는 것이 아니므로 SDK callback, recording query와 서버 로그를 사용합니다. RTP/RTCP는 presence와 publish 상태를 확인한 뒤 보는 전송 계층 증거입니다.
9. 정리
stream과 RTP의 관계는 이렇게 정리하면 됩니다.
운영에서는 다음 순서로 구분하면 됩니다.
RTP는 stream을 운반하는 네트워크 조각이고, Cloud Recording 디버깅은 RTP packet 단위가 아니라 channel presence, host role, publish/subscription 상태에서 시작해야 합니다.
검증한 기준
- RFC 3550: RTP: A Transport Protocol for Real-Time Applications
- MDN: MediaStream
- MDN: RTCPeerConnection.addTrack
- MDN: RTCRtpSender
- MDN: RTCRtpReceiver
- W3C: WebRTC Statistics API
- Agora Android Video SDK 4.x: IRtcEngine API
- Agora Support: How can I stop cloud recording?
- Agora Support: Recording Not Generated Due to No Published Audio or Video Streams in Channel