STT 오디오 처리/세션 (Go) — VAD·필터·turn history 코드레벨 동작 흐름 P2코드레벨

마지막 업데이트 2026-07-29

STT 오디오 처리/세션 (Go) — VAD·필터·turn history …: 입력: 개요 · 범위, 주요 처리 단계: 오디오 처리 internal/audio/, 결과: 파일 · 라인 레퍼런스 흐름
동작 흐름 요약
  1. 입력: 개요 · 범위
  2. 주요 처리 단계: 오디오 처리 internal/audio/
  3. 결과: 파일 · 라인 레퍼런스
작성일: 2026-06-14 대상: 개발자 — STT 서버 내부 오디오/세션 모듈 파악 핵심 파일: apps/stt/internal/{audio,session}/
💬 대화로 먼저 이해하기 — "녹음실 장비·서류함 비유" (비개발자·처음 읽는 사람용)
Q이 문서는 STT 배치 서버 문서(#4)와 뭐가 다른가요?
A#4가 속기사무소의 업무 흐름이라면, 이 문서는 그 사무소가 쓰는 녹음실 장비(오디오 처리)와 서류함(세션 상태) 설명서예요.
Q장비실(internal/audio)에서는 무슨 일을 하나요?
A녹음 테이프를 속기사에게 주기 전에 손질해요 — 저음 웅웅거림 제거(HPF 195Hz), 고음 잡음 컷(LPF 13kHz), 너무 큰 소리 눌러주기(Limiter 89%), 그리고 앞뒤 무음 잘라내기(RMS 기반 trim)까지.
Q서류함(internal/session)에는 뭐가 들어 있나요?
A지금 진행 중인 턴의 오디오(CurrentTurn), 최근 턴 묶음(TurnHistory), 화자별 텍스트 기록(ConversationHistory)이요. 속기 요청 직전에 스냅샷+리셋해서 다음 턴 소리가 이번 요청에 섞이지 않게 합니다.
Q손대기 전에 꼭 알아야 할 규칙이 있다면요?
A테이프 결합(BuildCombinedAudioWithCurrent)은 항상 AI 목소리 먼저 — 순서를 바꾸면 #4의 화자 구분 규칙(First-Speaker Rule)이 뒤집혀요. 나머지 규칙은 아래 "함정 · 주의" 섹션과 session.go를 보세요.

개요 · 범위

STT 배치 서버(#4)의 하위 모듈 — 오디오 처리(filter/limiter/VAD/PCM)세션 상태(turn 버퍼·history·debounce). #4가 "흐름"이라면 본 문서는 그 흐름이 쓰는 "부품".

오디오 처리 internal/audio/

모듈역할
pcm.goPCM int16 ↔ float64 변환, WAV 인코딩/디코딩, AudioDuration(bytes, sr)
filter.goBiquad(2nd) + 6th-order HPF / 4th-order LPF Butterworth
limiter.go다이내믹 레인지 제한(ceiling), ProcessWithGain
processor.go파이프라인: HPF 195Hz(6th) → LPF 13kHz(4th) → Limiter 89%
vad.goRMS 기반 무음 trim·VAD
DefaultProcessorConfig: 16kHz, HPF 195Hz, LPF 13kHz, ceiling 0.89(89%). "Python 서버와 동일" 주석 — 기존 파이프라인 포팅. (클라이언트 STT 전용 HPF 100Hz/Limiter -3dBFS와는 별개 — #9)

무음 trim vad.go: TrimLeading/TrailingSilencePCM L42/119

TrimLeadingSilencePCM(pcm, threshold, keepMS, sampleRate)
TrimTrailingSilencePCM(...)        // RMS < threshold(int16, 예 200) = 무음
CalculateRMS(pcm)                  // 무음 판정 기준

#4의 handleBatchFinalize가 user leading/trailing·AI 무음을 이 함수들로 잘라낸다(keepMS만큼 보존).

세션 상태 internal/session/session.go, history.go

구조/함수역할
Session.CurrentTurn현재 턴 {AIAudio, UserAudio} bytes.Buffer (mutex 보호)
WriteAudioChunk(speaker, data)바이너리 수신분을 current turn 버퍼에 append
SnapshotAndResetCurrentTurnAPI 호출 전 원자 스냅샷+리셋(#4 핵심)
AddTurnToHistory턴을 history에 추가, MaxBatchTurns 초과분 앞에서 제거(슬라이딩)
BuildCombinedAudioWithCurrenthistory(AI+User 순) + 현재 턴 결합, MaxBatchBytes 초과 시 뒤쪽만 보존
ConversationHistory화자별 텍스트 히스토리(HistorySizePerSpeaker, 화자분리 컨텍스트)
BatchDebounceStatefinalize requestID 발급·최신성(IsLatest)
combined audio 순서 = AI 먼저: BuildCombinedAudioWithCurrent가 각 턴을 AIAudio → UserAudio 순으로 쓴다. 이 순서가 화자분리 First-Speaker Rule(첫 화자=AI, #4)의 전제.

함정 · 주의

  • AI 먼저 순서 고정: combined 빌드가 AI→User 순. 바꾸면 First-Speaker Rule 화자 라벨 반전(#4).
  • MaxBatchBytes는 뒤쪽 보존: 초과 시 data[len-max:]로 최신만 남김(앞쪽 오래된 컨텍스트 절삭).
  • trim threshold는 int16 범위: RMS threshold(예 200)는 int16 진폭 기준. dBFS와 혼동 금지.
  • 서버/클라이언트 DSP 별개: 서버 Processor(HPF 195/LPF 13k/89%)와 클라 STT DSP(#9, HPF 100/Limiter -3dB)는 서로 다른 단계.
  • mutex 보호: CurrentTurn/TurnHistory는 동시 접근(바이너리 수신 vs finalize goroutine)되므로 Lock 필수.

파일 · 라인 레퍼런스

파일/심볼역할
internal/audio/processor.go (DefaultProcessorConfig)HPF/LPF/Limiter 파이프라인
internal/audio/vad.go (TrimLeading/Trailing L42/119)무음 trim·RMS
internal/audio/pcm.goPCM/WAV 변환·duration
internal/session/session.go (Snapshot/AddTurn/BuildCombined)턴 버퍼·history·debounce
internal/session/history.go화자별 텍스트 히스토리