주요 구현사항: - 5초마다 Redis 오디오 버퍼를 배치 처리하여 텍스트 변환 - WebSocket 실시간 오디오 수신 및 Redis Stream 저장 - Azure Speech Service 연동 (시뮬레이션 모드 포함) - Event Hub 이벤트 발행 (AI 서비스 연동) 아키텍처: Frontend (오디오 캡처) → WebSocket → STT Service → Redis Stream (버퍼) → @Scheduled(5초) 배치 처리 → Azure Speech API → DB 저장 + Event Hub 발행 → AI Service (텍스트 분석) 핵심 컴포넌트: 1. AudioWebSocketHandler - WebSocket 연결 관리 - JSON/Binary 메시지 처리 - Redis Stream에 오디오 저장 2. AudioBufferService - Redis Stream 오디오 버퍼링 - 청크 조회 및 병합 - 활성 회의 관리 3. AzureSpeechService - Azure Speech SDK 연동 - 배치 단위 음성 인식 - 시뮬레이션 모드 지원 4. AudioBatchProcessor - @Scheduled(5초) 배치 작업 - 오디오 → 텍스트 변환 - TranscriptSegment DB 저장 - Event Hub 이벤트 발행 배치 방식의 장점: ✅ 비용 최적화: Azure API 호출 1/5 감소 ✅ 문맥 이해: 5초 분량 한 번에 처리로 정확도 향상 ✅ AI 효율: 일정량 텍스트 주기적 생성 ✅ 안정성: 재시도 로직 구현 용이 설정: - Azure Speech: eastus, ko-KR - Redis: 포트 6379, DB 3 - WebSocket: /ws/audio - 배치 주기: 5초 (고정) 다음 단계: - 프론트엔드 WebSocket 클라이언트 구현 - 실제 Azure Speech API 키 설정 - E2E 통합 테스트 (STT → AI → Frontend) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
13 KiB
STT 서비스 배치 방식 구현 완료 보고서
작성일: 2025-10-27 작성자: 준호 (Backend Developer)
📋 개요
STT 서비스를 배치 처리 방식으로 구현 완료
- 핵심: 5초마다 Redis에 축적된 오디오를 배치 처리하여 Azure Speech로 텍스트 변환
- 장점: 비용 절감, 문맥 이해 향상, AI 분석 효율 증가
- 기술: Java/Spring Boot, Azure Speech SDK, Redis Stream, WebSocket
🏗️ 최종 아키텍처
┌──────────────────────────────────────────────────────────┐
│ Frontend (회의 화면) │
│ - 오디오 캡처 (매초) │
│ - WebSocket으로 실시간 전송 │
└────────────────────┬─────────────────────────────────────┘
│ WebSocket (ws://localhost:8084/ws/audio)
↓
┌──────────────────────────────────────────────────────────┐
│ STT Service (Java/Spring Boot) │
│ 포트: 8084 │
├──────────────────────────────────────────────────────────┤
│ 1. AudioWebSocketHandler │
│ - WebSocket 메시지 수신 (JSON/Binary) │
│ - Base64 디코딩 │
│ ↓ │
│ 2. AudioBufferService │
│ - Redis Stream에 오디오 청크 저장 │
│ - Key: audio:stream:{meetingId} │
│ - TTL: 1분 │
│ ↓ │
│ 3. Redis Stream (버퍼) │
│ - 오디오 청크 임시 저장 │
│ - 5초 분량 축적 │
│ ↓ │
│ 4. AudioBatchProcessor (@Scheduled) │
│ - 5초마다 실행 │
│ - Redis에서 청크 조회 → 병합 │
│ - Azure Speech API 호출 │
│ - TranscriptSegment DB 저장 │
│ - Event Hub 이벤트 발행 │
└────────────────────┬─────────────────────────────────────┘
│ Event Hub (transcription-events)
↓
┌──────────────────────────────────────────────────────────┐
│ AI Service (Python/FastAPI) │
│ 포트: 8086 │
│ - Event Hub에서 텍스트 수신 │
│ - Redis에 텍스트 축적 (슬라이딩 윈도우 5분) │
│ - Claude API 분석 → SSE로 프론트엔드 전송 │
└──────────────────────────────────────────────────────────┘
🔧 구현 컴포넌트
1. Redis Stream 설정
파일: stt/src/main/java/com/unicorn/hgzero/stt/config/RedisStreamConfig.java
@Configuration
public class RedisStreamConfig {
@Bean(name = "audioRedisTemplate")
public RedisTemplate<String, byte[]> audioRedisTemplate(...) {
// 오디오 데이터 저장용
}
}
2. AudioChunkDto
파일: stt/src/main/java/com/unicorn/hgzero/stt/dto/AudioChunkDto.java
@Data
@Builder
public class AudioChunkDto {
private String meetingId;
private byte[] audioData; // 오디오 데이터
private Long timestamp; // 타임스탬프 (밀리초)
private Integer chunkIndex; // 순서
private String format; // audio/webm
private Integer sampleRate; // 16000
}
3. AudioBufferService
파일: stt/src/main/java/com/unicorn/hgzero/stt/service/AudioBufferService.java
핵심 기능:
bufferAudioChunk(): 오디오 청크를 Redis Stream에 저장getAudioChunks(): 회의별 오디오 청크 조회mergeAudioChunks(): 청크 병합 (5초 분량)clearProcessedChunks(): 처리 완료 후 Redis 정리
4. AudioWebSocketHandler
파일: stt/src/main/java/com/unicorn/hgzero/stt/controller/AudioWebSocketHandler.java
핵심 기능:
- WebSocket 연결 관리
- 텍스트 메시지 처리 (JSON 형식)
type: "start": 녹음 시작type: "chunk": 오디오 청크 (Base64)type: "stop": 녹음 종료
- 바이너리 메시지 처리 (직접 오디오 데이터)
5. AzureSpeechService
파일: stt/src/main/java/com/unicorn/hgzero/stt/service/AzureSpeechService.java
핵심 기능:
recognizeAudio(): 오디오 데이터를 텍스트로 변환- Azure Speech SDK 사용
- 시뮬레이션 모드 지원 (API 키 없을 때)
설정:
azure:
speech:
subscription-key: ${AZURE_SPEECH_SUBSCRIPTION_KEY:}
region: ${AZURE_SPEECH_REGION:eastus}
language: ko-KR
6. AudioBatchProcessor
파일: stt/src/main/java/com/unicorn/hgzero/stt/service/AudioBatchProcessor.java
핵심 기능:
@Scheduled(fixedDelay = 5000): 5초마다 실행- 활성 회의 목록 조회
- 각 회의별 오디오 처리:
- Redis에서 오디오 청크 조회
- 청크 병합 (5초 분량)
- Azure Speech API 호출
- TranscriptSegment DB 저장
- Event Hub 이벤트 발행
- Redis 정리
📊 데이터 흐름
1. 오디오 수신 (실시간)
Frontend → STT Service:
// WebSocket 연결
const ws = new WebSocket('ws://localhost:8084/ws/audio');
// 녹음 시작
ws.send(JSON.stringify({
type: 'start',
meetingId: 'meeting-123'
}));
// 오디오 청크 전송 (매초)
ws.send(JSON.stringify({
type: 'chunk',
meetingId: 'meeting-123',
audioData: base64AudioData,
timestamp: Date.now(),
chunkIndex: 0,
format: 'audio/webm',
sampleRate: 16000
}));
STT Service:
// AudioWebSocketHandler
handleTextMessage() {
AudioChunkDto chunk = parseMessage(message);
audioBufferService.bufferAudioChunk(chunk);
}
// AudioBufferService
bufferAudioChunk(chunk) {
redis.opsForStream().add("audio:stream:meeting-123", chunk);
}
2. 배치 처리 (5초마다)
@Scheduled(fixedDelay = 5000)
public void processAudioBatch() {
// 1. 활성 회의 조회
Set<String> meetings = audioBufferService.getActiveMeetings();
for (String meetingId : meetings) {
// 2. 오디오 청크 조회 (최근 5초)
List<AudioChunkDto> chunks = audioBufferService.getAudioChunks(meetingId);
// 3. 청크 병합
byte[] mergedAudio = audioBufferService.mergeAudioChunks(chunks);
// 4. Azure Speech 인식
RecognitionResult result = azureSpeechService.recognizeAudio(mergedAudio);
// 5. DB 저장
saveTranscriptSegment(meetingId, result);
// 6. Event Hub 발행
publishTranscriptionEvent(meetingId, result);
// 7. Redis 정리
audioBufferService.clearProcessedChunks(meetingId);
}
}
3. Event Hub 이벤트 발행
TranscriptionEvent.SegmentCreated event = TranscriptionEvent.SegmentCreated.of(
segmentId,
meetingId,
result.getText(),
"참석자",
LocalDateTime.now(),
5.0, // duration
result.getConfidence(),
warningFlag
);
eventPublisher.publishAsync("transcription-events", event);
4. AI 서비스 수신 (Python)
# AI Service (Python)
async def on_event(partition_context, event):
event_data = json.loads(event.body_as_str())
if event_data["eventType"] == "TranscriptSegmentReady":
meetingId = event_data["meetingId"]
text = event_data["text"]
# Redis에 텍스트 축적
await redis_service.add_transcript_segment(meetingId, text, timestamp)
# Claude API 분석 트리거
await analyze_and_emit_suggestions(meetingId)
⚙️ 설정 및 실행
1. 환경 변수 설정
IntelliJ 실행 프로파일 (.run/STT.run.xml):
<option name="env">
<map>
<entry key="AZURE_SPEECH_SUBSCRIPTION_KEY" value="your-key-here"/>
<entry key="AZURE_SPEECH_REGION" value="eastus"/>
<entry key="REDIS_HOST" value="20.249.177.114"/>
<entry key="REDIS_PORT" value="6379"/>
<entry key="REDIS_PASSWORD" value="Hi5Jessica!"/>
<entry key="EVENTHUB_CONNECTION_STRING" value="Endpoint=sb://..."/>
</map>
</option>
2. 서비스 시작
# IntelliJ에서 STT 실행 프로파일 실행
# 또는
# Gradle로 실행
cd stt
./gradlew bootRun
3. 로그 확인
tail -f stt/logs/stt.log
예상 로그:
2025-10-27 12:00:00 - Azure Speech Service 초기화 완료 - Region: eastus, Language: ko-KR
2025-10-27 12:00:05 - WebSocket 연결 성공 - sessionId: abc123
2025-10-27 12:00:10 - 오디오 청크 버퍼링 완료 - meetingId: meeting-123
2025-10-27 12:00:15 - 배치 처리 시작 - 활성 회의: 1개
2025-10-27 12:00:15 - 음성 인식 성공: 신제품 개발 일정에 대해 논의하고 있습니다.
2025-10-27 12:00:15 - Event Hub 이벤트 발행 완료
🧪 테스트 방법
1. WebSocket 테스트 (JavaScript)
const ws = new WebSocket('ws://localhost:8084/ws/audio');
ws.onopen = () => {
console.log('WebSocket 연결 성공');
// 녹음 시작
ws.send(JSON.stringify({
type: 'start',
meetingId: 'test-meeting'
}));
// 5초 동안 오디오 청크 전송 (시뮬레이션)
for (let i = 0; i < 5; i++) {
setTimeout(() => {
ws.send(JSON.stringify({
type: 'chunk',
meetingId: 'test-meeting',
audioData: 'dGVzdCBhdWRpbyBkYXRh', // Base64
timestamp: Date.now(),
chunkIndex: i
}));
}, i * 1000);
}
// 10초 후 녹음 종료
setTimeout(() => {
ws.send(JSON.stringify({
type: 'stop'
}));
}, 10000);
};
ws.onmessage = (event) => {
console.log('응답:', JSON.parse(event.data));
};
2. Redis 확인
redis-cli -h 20.249.177.114 -p 6379 -a Hi5Jessica!
# 활성 회의 목록
SMEMBERS active:meetings
# 오디오 스트림 확인
XRANGE audio:stream:test-meeting - +
# 스트림 길이
XLEN audio:stream:test-meeting
3. 데이터베이스 확인
-- 텍스트 세그먼트 조회
SELECT * FROM transcript_segment
WHERE recording_id = 'test-meeting'
ORDER BY timestamp DESC
LIMIT 10;
📈 성능 특성
| 항목 | 값 | 비고 |
|---|---|---|
| 배치 주기 | 5초 | @Scheduled(fixedDelay = 5000) |
| 지연 시간 | 최대 5초 | 사용자 경험에 영향 없음 |
| Azure API 호출 빈도 | 1/5초 | 실시간 방식 대비 1/5 감소 |
| Redis TTL | 1분 | 처리 지연 대비 |
| 오디오 청크 크기 | 가변 | 프론트엔드 전송 주기에 따름 |
✅ 장점
-
비용 최적화
- Azure Speech API 호출 빈도 1/5 감소
- 비용 절감 효과
-
문맥 이해 향상
- 5초 분량을 한 번에 처리
- 문장 단위 인식으로 정확도 향상
-
AI 분석 효율
- 일정량의 텍스트가 주기적으로 생성
- AI가 분석하기 적합한 분량
-
안정성
- 재시도 로직 구현 용이
- 일시적 네트워크 오류 대응
-
확장성
- 여러 회의 동시 처리 가능
- Redis로 분산 처리 가능
⚠️ 주의사항
1. Azure Speech API 키 관리
.run/STT.run.xml에 실제 API 키 설정 필요- Git에 커밋하지 않도록 주의
2. Redis 연결
- Redis 서버가 실행 중이어야 함
- 연결 정보 확인 필요
3. Event Hub 설정
- Event Hub 연결 문자열 필요
- AI 서비스와 동일한 Event Hub 사용
4. 배치 주기 조정
- 5초 주기는 기본값
- 필요시
application.yml에서 조정 가능
🔄 다음 단계
-
프론트엔드 연동
- WebSocket 클라이언트 구현
- 오디오 캡처 및 전송
-
E2E 테스트
- 실제 음성 데이터로 테스트
- Azure Speech API 연동 검증
-
AI 서비스 통합 테스트
- Event Hub 통신 확인
- SSE 스트리밍 검증
-
성능 최적화
- 배치 주기 조정
- Redis 메모리 사용량 모니터링
📞 문의
STT 서비스: 준호 (Backend Developer) AI 서비스: 서연 (AI Specialist)
최종 업데이트: 2025-10-27