# 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) │ │ 포트: 8087 │ │ - Event Hub에서 텍스트 수신 │ │ - Redis에 텍스트 축적 (슬라이딩 윈도우 5분) │ │ - Claude API 분석 → SSE로 프론트엔드 전송 │ └──────────────────────────────────────────────────────────┘ ``` --- ## 🔧 구현 컴포넌트 ### 1. Redis Stream 설정 **파일**: `stt/src/main/java/com/unicorn/hgzero/stt/config/RedisStreamConfig.java` ```java @Configuration public class RedisStreamConfig { @Bean(name = "audioRedisTemplate") public RedisTemplate audioRedisTemplate(...) { // 오디오 데이터 저장용 } } ``` ### 2. AudioChunkDto **파일**: `stt/src/main/java/com/unicorn/hgzero/stt/dto/AudioChunkDto.java` ```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 키 없을 때) **설정**: ```yaml 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초마다 실행 - 활성 회의 목록 조회 - 각 회의별 오디오 처리: 1. Redis에서 오디오 청크 조회 2. 청크 병합 (5초 분량) 3. Azure Speech API 호출 4. TranscriptSegment DB 저장 5. Event Hub 이벤트 발행 6. Redis 정리 --- ## 📊 데이터 흐름 ### 1. 오디오 수신 (실시간) **Frontend → STT Service:** ```javascript // 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:** ```java // AudioWebSocketHandler handleTextMessage() { AudioChunkDto chunk = parseMessage(message); audioBufferService.bufferAudioChunk(chunk); } // AudioBufferService bufferAudioChunk(chunk) { redis.opsForStream().add("audio:stream:meeting-123", chunk); } ``` ### 2. 배치 처리 (5초마다) ```java @Scheduled(fixedDelay = 5000) public void processAudioBatch() { // 1. 활성 회의 조회 Set meetings = audioBufferService.getActiveMeetings(); for (String meetingId : meetings) { // 2. 오디오 청크 조회 (최근 5초) List 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 이벤트 발행 ```java 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) ```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`): ```xml ``` ### 2. 서비스 시작 ```bash # IntelliJ에서 STT 실행 프로파일 실행 # 또는 # Gradle로 실행 cd stt ./gradlew bootRun ``` ### 3. 로그 확인 ```bash 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) ```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 확인 ```bash 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. 데이터베이스 확인 ```sql -- 텍스트 세그먼트 조회 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분 | 처리 지연 대비 | | **오디오 청크 크기** | 가변 | 프론트엔드 전송 주기에 따름 | --- ## ✅ 장점 1. **비용 최적화** - Azure Speech API 호출 빈도 1/5 감소 - 비용 절감 효과 2. **문맥 이해 향상** - 5초 분량을 한 번에 처리 - 문장 단위 인식으로 정확도 향상 3. **AI 분석 효율** - 일정량의 텍스트가 주기적으로 생성 - AI가 분석하기 적합한 분량 4. **안정성** - 재시도 로직 구현 용이 - 일시적 네트워크 오류 대응 5. **확장성** - 여러 회의 동시 처리 가능 - 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`에서 조정 가능 --- ## 🔄 다음 단계 1. **프론트엔드 연동** - WebSocket 클라이언트 구현 - 오디오 캡처 및 전송 2. **E2E 테스트** - 실제 음성 데이터로 테스트 - Azure Speech API 연동 검증 3. **AI 서비스 통합 테스트** - Event Hub 통신 확인 - SSE 스트리밍 검증 4. **성능 최적화** - 배치 주기 조정 - Redis 메모리 사용량 모니터링 --- ## 📞 문의 **STT 서비스**: 준호 (Backend Developer) **AI 서비스**: 서연 (AI Specialist) --- **최종 업데이트**: 2025-10-27