본문으로 건너뛰기

브라우저부터 Opus로 보내고 S3에 30초 세그먼트로 쌓는다

맥락

오디오를 S3 에 남기는 경로를 아직 한 줄도 안 만들었다. software.amazon 이 SES 발신에만 쓰이고 S3 SDK 호출이 서버 코드에 하나도 없다.지금이 포맷을 바꿀 수 있는 마지막 시점이고, 쌓기 시작하면 이미 쌓인 회의를 못 읽게 된다.

직전 설계는 브라우저가 PCM16/24 kHz/mono 를 보내고 realtime 이 그것을 5 MiB 멀티파트로 S3 에 올린 뒤, 종료 후 워커가 이어 읽으며 Opus 로 인코딩하는 것이었다. 그 설계를 지탱한 논거가 둘이었다.

  1. STT 에 손실 압축을 먹이지 않는다 — 정확도가 최우선이다.
  2. 압축하면 5 MiB 파트 하나가 21분이 되어 유실 노출 창이 12배가 된다.

두 번째 논거를 다시 재 보면서 그것이 압축의 대가가 아니라 멀티파트의 대가라는 것이 드러났다. 멀티파트 파트는 마지막을 빼고 5 MiB 이상이어야 하므로, 「서버 RAM 에만 있는 오디오의 양」이 코덱 비트레이트에 끌려간다.

포맷5 MiB 를 채우는 데
PCM16 24 kHz (48 kB/s)1분 49초
Opus 32 kbps (4 kB/s)21분 50초

압축을 잘할수록 더 오래 위험해진다. 방향이 거꾸로 된 결합이고, 이것이 붙어 있는 한 포맷을 자유롭게 고를 수 없다.

첫 번째 논거도 다시 봤다. 회의 전사는 이미 대부분 Opus 위에서 돈다 — WebRTC 의 오디오 코덱이 Opus 이기 때문이다. 미지의 위험이 아니다. 그리고 ADR-0019가 고른 pyannote.ai 는 압축 오디오를 권장한다(1 GiB 파일 한도).

결정

  • S3 적재를 멀티파트에서 「30초마다 오브젝트 하나」로 바꾼다. 유실 창이 코덱이 아니라 우리가 고르는 값이 된다. flush 는 크기가 아니라 타이머로 건다 — 바이트 임계면 조용한 회의가 영영 안 밀린다.
  • 브라우저가 MediaRecorder("audio/webm;codecs=opus") 로 32 kbps mono 를 보낸다. 바꾼 이유는 비용이 아니라 신뢰성이다 — 384 kbps 를 다섯 시간 붙들면 나쁜 업링크에서 프레임이 날아가고, 날아간 소리는 회의가 끝나고 나서야 안다.
  • realtime 은 받은 바이트를 해석하지 않는다. Soniox 가 webmaudio_format: auto 로 받으므로 디코딩 없이 그대로 통과시키고, 같은 바이트를 S3 에도 쓴다. 스트림마다 인코더가 붙지 않으므로 realtime 의 CPU 가 회의 수에 비례하지 않는다.
  • 조립은 ffmpeg -f concat -c copy 다. 디코딩도 인코딩도 없다. 제출 형식이 이미 컨테이너라 변환 단계가 사라진다.
  • WAV 를 쓰지 않고, 화자 분리 전에 16 kHz WAV 로 정규화하지도 않는다. 전자는 헤더의 전체 길이 필드가 회의가 끝나야 아는 값이라서고, 후자는 그것이 자체 호스팅 pyannote.audio 모델이 받는 형식이지 호스팅 API 의 요구가 아니라서다 — 오히려 5시간이 576 MB 가 되어 한도 쪽으로 민다.
  • 시간 좌표계를 세 층으로 가른다runstt_streamsegment. 벤더가 재연결하면 start_ms 가 0 으로 리셋되므로 그것을 흡수할 층이 따로 있어야 한다. 자세한 규칙은 SA.

대안

대안 1 — PCM 유지 + 세그먼트. 멀티파트만 바꾸고 포맷은 그대로 둔다. 되돌릴 수 없는 선택이 없다. 기각한 이유: 유실 창 문제는 풀리지만 업링크 혼잡이 그대로 남는다. 그리고 pyannote 가 raw 를 못 받으므로 제출 전 변환이 여전히 필수라, "PCM 은 조립이 공짜라 유리하다"는 이득이 실현되지 않는다. 샘플레이트를 16 kHz 로 내려 256 kbps 로 줄이는 것까지 검토했으나 자릿수가 안 바뀐다.

대안 2 — WebCodecs AudioEncoder 로 raw Opus 를 만든다. 청크마다 timestamp·duration 이 와서 시간 계산이 API 로 해결된다. 기각한 이유: Firefox for Android 에 없고 Safari 는 26 부터다. iOS 는 모든 브라우저가 WebKit 이라 다른 브라우저를 깔아도 못 푼다. 그리고 raw Opus 는 컨테이너가 없어 Ogg 뮤서를 우리가 써야 한다. 세그먼트마다 시간을 우리가 이미 적으므로 그 API 이점을 살 필요가 없다.

대안 3 — 멀티파트 유지 + 종료 후 인코딩(직전 설계). 기각한 이유: 위 맥락 그대로다.

결과

  • 전송 = 저장 = 제출이 한 포맷이다. 변환 단계가 파이프라인에서 사라진다.
  • 오디오 물량이 12배 줄었다. 월 3.9 TB 로 추정하던 것이 ≈ 330 GB 다. 다만 이건 코덱을 바꾼 결과이지 그 추정이 맞다는 뜻이 아니다 — 입력 가정은 여전히 미검증이다.
  • 원본 PCM 7일 보관 규칙이 없어진다. 브라우저부터 Opus 라 되돌릴 원본이 애초에 없다. 원본 품질로 재전사할 수 없다는 것이 이 결정이 감수하는 것이다.
  • uploadId 라는 숨은 상태가 없어진다. 태스크가 바뀌어도 넘길 것이 0개이고, 끊긴 회의의 복구가 「프리픽스 나열」이 된다 — DB 가 틀려도 오디오는 찾는다.
  • ADR-0003의 "binary 1MB(PCM 업로드)" 문구가 낡았다. 상한 값 자체는 유효하지만 실려 오는 것이 PCM 이 아니다.
  • 되돌릴 수 없는 것이 하나 있다 — 손실 압축이다. 이 결정을 뒤집으려면 같은 회의를 PCM 과 Opus 32 kbps 로 각각 전사해 정확도를 비교한 실측이 필요하고, 그 실측 전에는 이 ADR 이 기대고 있는 성질(“Opus 로도 전사 정확도가 충분하다”)이 확인되지 않은 상태다.