Skip to content

fix: rtcp-feedback-negotiation/#90 - #91

Merged
itzjb merged 2 commits into
mainfrom
fix/rtcp-feedback-negotiation/#90
Aug 11, 2026
Merged

fix: rtcp-feedback-negotiation/#90#91
itzjb merged 2 commits into
mainfrom
fix/rtcp-feedback-negotiation/#90

Conversation

@hjbin-25

@hjbin-25 hjbin-25 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

변경 내용

송출 시작 직후 몇 초간 클라이언트 미리보기 영상의 정지 영역(벽·천장)만 파괴되고 자가복구되는 증상을 고쳤어요. 디코더 레퍼런스 손실이 원인이에요.

internal/session/video_codec.goofferedVideoCodecs가 클라이언트 offer를 파싱해 RTPCodecCapability를 재구성할 때 RTCPFeedback을 채우지 않았어요. 이 값이 prepareOutputForOfferSetCodecPreferences로 비디오 트랜시버에 통째로 덮어써지고, Pion은 answer의 a=rtcp-fb 라인을 협상된 코덱의 RTCPFeedback에서 생성하므로 answer에 피드백 라인이 한 줄도 실리지 않았어요.

비디오 m-line은 sendrecv 하나뿐이라 인그레스·이그레스 양방향 모두 NACK · PLI · FIR · transport-cc · goog-remb가 미협상 상태였어요.

이제 offer SDP의 a=rtcp-fb 라인을 페이로드 타입별로 읽어 RTPCodecCapability.RTCPFeedback에 채워요. 서버가 임의로 피드백을 추가하지 않고 클라이언트가 제안한 것만 그대로 되돌려줘요. 코덱 선택 순서(클라이언트 offer 순서)는 바뀌지 않아요.

실측 — answer의 a=rtcp-fb 라인 수:

피드백 VP8 선호 (전 → 후) H.264 선호 (전 → 후)
nack 0 → 2 0 → 1
nack pli 0 → 2 0 → 1
ccm fir 0 → 1 0 → 1
goog-remb 0 → 1 0 → 1
transport-cc 0 → 1 0 → 1

answer는 협상된 코덱 하나(+RTX)만 광고하므로 offer처럼 수십 줄이 되지 않는 것이 정상이에요.

확인 방법

실제 ICE·DTLS 핸드셰이크를 맺고 클라이언트가 협상 후 적용하는 코덱 파라미터를 대조했어요. pion은 NACK·PLI·transport-cc 인터셉터 구동 여부를 SDP 텍스트가 아니라 이 값으로 결정해요.

수정 전 수정 후
Receiver().GetParameters() 피드백 [] ccm fir, goog-remb, nack, nack pli, transport-cc
Sender().GetParameters() 피드백 [] 동일
ICE/DTLS 연결 성공 성공

수정 전에도 연결 자체는 정상이고 피드백만 비어요 — "연결 실패"가 아니라 "초반 화질 깨짐 후 자가복구"였던 증상과 맞물려요.

go test ./internal/session -run 'RTCPFeedback' -v
go test ./internal/session ./internal/media -count=1
  • TestAnswerRetainsOfferedRTCPFeedback (answer SDP 레벨) — 수정 전 RED → 수정 후 PASS
  • TestConnectedPeerNegotiatesRTCPFeedback (연결 후 런타임 파라미터) — 수정 전 RED → 수정 후 PASS
  • 두 패키지 모두 ok, -count=5 반복에서도 안정적이며 패키지 실행시간 변화 없음 (0.59s)
  • gofmt -l internal/session 빈 출력, go vet ./internal/session 통과

검증하지 못한 것

  • 미디어 실제 흐름은 미확인이에요. 통합 테스트의 더미 H.264 바이트가 디코딩 불가라 egress 파이프라인이 프레임을 내지 않아요. 검증 범위는 협상 계층까지예요.
  • 브라우저 E2E는 못 했어요. cmd/server 빌드가 otel로 막혀 있는데, 원인은 저장소가 아니라 로컬 모듈 캐시 손상이에요 (go mod verifyx/sys·x/text·gonum·protobuf 등 다수에 "dir has been modified"). 별도로 다뤄야 해요.

go build ./...는 otel 모듈 캐시 문제(semconv/v1.37.0: undefined: ErrorTypeOther)로 실패하는데, 이번 변경과 무관한 기존 이슈예요.

범위 밖

  • drainRTCP의 PLI/FIR 분기 처리(2순위) — 현재 egress 인코더는 stdin 기반 별도 ffmpeg 프로세스라 실행 중 온디맨드 IDR을 강제할 채널이 없고, 인코더 재기동이냐 인프로세스 교체냐는 미결 설계 결정이에요. 별도 이슈로 다뤄요.
  • GOP 단축 — 미리보기 레그 IDR 주기는 실측 1.000초로 이미 짧아요.
  • YouTube RTMP egress([FEAT] 유튜브 송출 RTMP 구현 #29) 영향 여부 — 별도 확인 항목이에요.

문서

docs/.gitignore에 등록되어 있어 문서 갱신은 이 PR에 포함되지 않아요. 로컬에서 아래 두 문서를 갱신해 뒀어요.

Closes #90

🤖 Generated with Claude Code

hjbin-25 and others added 2 commits August 11, 2026 15:04
offeredVideoCodecs가 offer SDP에서 코덱을 재구성할 때 RTCPFeedback을 채우지
않아, SetCodecPreferences가 트랜시버 코덱 선호를 덮어쓰면서 answer의
a=rtcp-fb 라인이 전부 사라졌다. NACK · PLI · FIR · transport-cc · goog-remb가
인그레스·이그레스 양방향 모두 미협상 상태가 되어 송출 초반 몇 초간 디코더
레퍼런스가 손실됐다.

a=rtcp-fb 라인을 페이로드 타입별로 읽어 RTPCodecCapability.RTCPFeedback에
채운다. 클라이언트가 제안한 피드백만 그대로 되돌려주며, 코덱 선택 순서는
바뀌지 않는다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
실제 ICE·DTLS 핸드셰이크를 마친 뒤 클라이언트가 적용하는 코덱 파라미터를
검증한다. pion은 NACK·PLI·transport-cc 인터셉터 구동 여부를 SDP 텍스트가
아니라 이 협상 파라미터로 결정하므로, 라이브 세션에서 피드백이 실제로
동작하는지는 이 값이 판별한다.

수정 전 코드에서는 receiver·sender 양쪽 모두 빈 리스트를 반환하며 실패한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@itzjb
itzjb marked this pull request as ready for review August 11, 2026 10:26
@itzjb
itzjb requested a review from a team as a code owner August 11, 2026 10:26
@itzjb
itzjb merged commit b7a4fce into main Aug 11, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: WebRTC answer SDP가 offer의 rtcp-fb를 누락해 송출 초반 화면이 깨짐

3 participants