마지막 업데이트 2026-07-29
| 항목 | 상태 |
|---|---|
| 증상 | 영상 재생 중 소리가 먹먹함. 구간에 따라 심하고 덜함(중간중간) |
| 재현 조건 | iPad + 영상 loopback ON. ?videoLoopback=off(직결)에서는 발생하지 않음 |
| 확정된 원인 층 | 브라우저 WebRTC 오디오 처리 경로(음성 대역). 우리 코드 층 아님 |
| 기각된 가설 | 코덱 협상 · 소스/ctx 대역 · 대역 추정(BWE) 비트레이트 하락 · Bluetooth HFP · 적응성 열화 |
| 즉시 가능한 대응 | 입장 화면 Loopback OFF 선택 → 영상만 직결 출력(코드·배포 불필요) |
| 대가 | 영상 오디오가 EC 활성 중 덕킹으로 약 12dB 감쇠(PPI-1101 회귀) |
iOS는 EC(voice-processing)가 활성인 동안 미디어 카테고리 재생을 덕킹으로 감쇠시킨다(PPI-1101). 이를 회피하려고 영상 오디오를 WebAudio로 빼내 같은 페이지 안의 WebRTC loopback으로 되받아 <audio>.srcObject로 재생한다. iOS가 이를 "통화 상대 음성"으로 분류해 덕킹을 면제하기 때문이다.
<audio>.srcObject덕킹 면제 · 대역 2~4kHz로 제한source(인코드 전 hub)와 remote(디코드 후) 스펙트럼을 같은 방식으로 재고, 송신 stats를 5초 간격으로 함께 남겼다. 값은 iPad 실기기 세션(2026-07-29 00:54) 로그다.
| 경과 | bitrateKbps | source rolloff | remote rolloff | 차이 |
|---|---|---|---|---|
| 1.2s | 148.3 | 10723 | 12176 | 무효 (remoteRms 0.002 = 램프업 구간) |
| 6.8s | 260.9 | 9105 | 1910 | −7195 |
| 12.1s | 260.9 | 3691 | 2707 | −984 |
| 17.4s | 260.9 | 3973 | 2824 | −1149 |
| 22.6s | 260.9 | 7957 | 3059 | −4898 |
| 27.9s | 261.1 | 6199 | 2449 | −3750 |
| 33.1s | 260.9 | 5332 | 3129 | −2203 |
| 38.4s | 256.0 | 5777 | 3996 | −1781 |
유효 샘플 7개 전부 remote 상한이 4kHz 아래다. source가 9.1kHz까지 있을 때 remote는 1.9kHz였다.
// Video audio loopback established / codec stats (iPad 실기기) negotiatedOpusFmtp: "cbr=1;maxaveragebitrate=256000;minptime=10;sprop-stereo=1;stereo=1;usedtx=0;useinbandfec=1" channels: 2 clockRate: 48000 sampleRate(ctx): 48000 bweDisabled: true bweFeedbackRemoved: 3 jitterBufferApplied: null
jitterBufferApplied: null — CriOS에는 jitterBufferTarget·playoutDelayHint가 둘 다 없어
수신 버퍼 고정이 실기기에서 전혀 적용되지 않는다. 재생 속도 출렁임 억제책이 무효라는 뜻이며 별도 이슈다.
| 가설 | 판정 | 기각 근거 |
|---|---|---|
| 코덱 협상/설정 문제 fmtp 미반영, 모노 다운믹스 |
기각 | fmtp 전량 반영, channels: 2, clockRate: 48000 |
| 소스·AudioContext 대역 제한 | 기각 | 12-20k 밴드 비중이 0이 아님(0.007~0.013) → Nyquist > 12kHz → ctx 48kHz 확정. 원본 파일도 풀밴드 |
| 대역 추정(BWE)이 비트레이트를 낮춤 | 기각 | transport-cc·goog-remb·헤더확장 제거(bweFeedbackRemoved: 3) 후에도 증상 유지. 비트레이트는 애초에 하락 없음 |
| 적응성 열화(시간 경과로 수렴) | 기각 | 대역 제한은 상시. 첫 샘플이 정상으로 보였던 건 remoteRms 0.002 램프업 구간의 노이즈였음 |
| Bluetooth HFP 강제 JUCE 사례와 같은 메커니즘 |
기각 | BT 미연결·내장 스피커에서도 재현. 또한 HFP라면 직결 출력도 함께 좁아져야 하는데 직결은 정상 |
| iOS 스피커 렌더 단계의 음색 차 | 기각 | 손실이 스피커 앞이 아니라 remote 탭(디코드 직후)에서 이미 관측됨 |
| 브라우저 WebRTC 오디오 처리가 음성 대역 | 유력(잔존) | 위 전부를 배제하고 남은 유일한 층. 직결(48kHz)은 정상이고 RTP 경로만 좁다는 비대칭과 일치 |
6-12k 비중이 완전히 0은 아니다(0.007~0.031).
1~15kHz 스윕 톤 진단이면 차단 주파수(4kHz=협대역 / 8kHz=16kHz 처리)와 기울기가 확정된다. 다만 처방은 바뀌지 않는다.
비교 대상이 된 JUCE 포럼 사례는 입력 사용 시 iOS가 HFP를 강제해 8/16kHz로 떨어지는 문제를 AVAudioSessionCategoryOptionAllowBluetooth 한 줄 제거로 해결한다. 같은 계열의 문제이지만, 그 레버가 웹에는 존재하지 않는다.
| 레버 | 네이티브 | 웹 |
|---|---|---|
| 오디오 세션 카테고리·모드 지정 | 가능 | 없음 |
| Bluetooth 프로파일 선택(HFP/A2DP) | 가능 | 없음 |
| WebRTC 오디오 처리 레이트 지정 | 가능 | 없음 |
| EQ·게인 등 신호 가공 | 가능 | 가능하나 손실 대역은 복원 불가 |
AVAudioSession을 직접 제어할 수 있는 네이티브 래퍼뿐이다.
| 안 | 내용 | 대가 / 한계 |
|---|---|---|
| A. 영상만 직결 현재 유력 |
입장 화면 Loopback OFF. TTS는 loopback 유지 → 상대 음량 균형 유지 | 영상 오디오가 덕킹으로 약 12dB 감쇠. 절대 음량은 기기 볼륨으로 보상(디지털 헤드룸 소모 없음) |
| B. 직결 + 게인 보상 | 직결 경로에 게인 튜너블 추가 | 영상 오디오가 풀스케일에 가까워 실용 상한 +3~6dB. 12dB는 클리핑. 덕킹이 동적이면 상쇄 자체가 무효 |
| C. loopback 유지 + EQ 상쇄 | ?videoLoopbackEqPresenceDb, EqMudDb. 살아 있는 대역이 ~3kHz까지이므로 presenceHz는 2500 권장 |
없는 대역은 복원 불가. 명료도 개선에 그침 |
| D. 영상 구간 마이크 캡처 정지 | 음성 경로 전환 자체를 회피 시도 | 범인이 loopback 자체면 무효. 모니터 아동 소리·STT·무음 감지에 영향 |
| E. EC 상시 off | voice-processing 회피 | 비추천 — 에코·clone 무음 사이드 이펙트 이력 구역 |
| F. 네이티브 래퍼 | AVAudioSession 직접 제어 |
유일한 근본 해결이나 범위가 큼(장기) |
모두 진단 목적이고 기본값에서 출력 동작을 바꾸지 않는다. apps/web/shared/lib/video-audio-loopback.ts, apps/web/shared/lib/webrtc-audio-loopback.ts
| 로그 / 기능 | 내용 | 튜너블 |
|---|---|---|
Video loopback spectrum sample |
source·remote의 대역별 비중, 센트로이드, 95% 롤오프, 4kHz 이상 비중, sinceEstablishedMs |
videoLoopbackSpectrumLog=off|allvideoLoopbackSpectrumLogLimit=N |
Video loopback codec stats |
5초 간격 실효 비트레이트·채널·clockRate·fmtp | videoLoopbackCodecStatsLog=off|allvideoLoopbackCodecStatsLimit=N |
| BWE 차단 (SDP munging) | transport-cc·goog-remb·transport-wide-cc·abs-send-time 제거. 가설은 기각됐으므로 롤백 후보 | videoLoopbackBwe=on으로 이전 동작 |
| 출력 EQ | loopback 경로에만 peaking 2단(mud/presence). 기본 0dB면 노드 미생성 | videoLoopbackEqPresenceDb 등 |
| 직결 A/B | loopback 없이 ctx.destination 직결만 사용 |
videoLoopback=off |
remoteRms가 무음 수준이면 노이즈를 읽는다(램프업 구간).
단일 샘플로 판정하지 말고 remoteRms가 유의미한 샘플만 볼 것. remote는 지터버퍼만큼 지연돼 source와 시점이 어긋나므로 추세로 비교해야 한다.
jitterBufferApplied: null 별도 이슈화 — CriOS에서 수신 버퍼 고정이 무효인 상태