fix:화자분리 정확도 개선 - 이중 녹음 방식 적용

DynamicsCompressor가 모든 화자의 음량을 동일하게 압축하여
화자 음성 특성이 파괴되는 문제 해결:
- MediaRecorder 1 (처리된 스트림): 실시간 Web Speech API용
- MediaRecorder 2 (원본 스트림): GCS 업로드 → 화자분리용
- 원본 오디오가 화자 음성 특성을 보존하여 분리 정확도 향상

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-11 21:18:16 +09:00
parent 79ed88fd10
commit da278b7422

View File

@@ -279,6 +279,8 @@ function MeetingDetail({ meetingId, onBack, showToast }) {
// refs
const mediaRecorderRef = useRef(null);
const audioChunksRef = useRef([]);
const rawRecorderRef = useRef(null); // 원본 오디오 녹음 (화자분리용)
const rawAudioChunksRef = useRef([]); // 원본 오디오 청크
const recognitionRef = useRef(null);
const streamRef = useRef(null);
const timerRef = useRef(null);
@@ -392,15 +394,24 @@ function MeetingDetail({ meetingId, onBack, showToast }) {
const processedStream = destination.stream;
// MediaRecorder (처리된 스트림 사용)
const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
? 'audio/webm;codecs=opus' : 'audio/webm';
// MediaRecorder 1: 처리된 스트림 → 실시간 STT 품질 향상용
const recorder = new MediaRecorder(processedStream, { mimeType, audioBitsPerSecond: 128000 });
audioChunksRef.current = [];
recorder.ondataavailable = (e) => { if (e.data.size > 0) audioChunksRef.current.push(e.data); };
recorder.start();
mediaRecorderRef.current = recorder;
// MediaRecorder 2: 원본 스트림 → GCS 업로드 (화자분리용)
// DynamicsCompressor 없는 원본 음성이 화자 음성 특성을 보존하여 분리 정확도 향상
const rawRecorder = new MediaRecorder(stream, { mimeType, audioBitsPerSecond: 128000 });
rawAudioChunksRef.current = [];
rawRecorder.ondataavailable = (e) => { if (e.data.size > 0) rawAudioChunksRef.current.push(e.data); };
rawRecorder.start();
rawRecorderRef.current = rawRecorder;
// Web Speech API
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SpeechRecognition) {
@@ -491,7 +502,20 @@ function MeetingDetail({ meetingId, onBack, showToast }) {
});
}
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
// 원본 MediaRecorder 중지 (화자분리용)
const rawRecorder = rawRecorderRef.current;
if (rawRecorder && rawRecorder.state !== 'inactive') {
await new Promise(resolve => {
rawRecorder.onstop = resolve;
rawRecorder.stop();
});
}
// 화자분리용: 원본(무가공) 오디오 우선, 없으면 처리된 오디오 폴백
const rawBlob = rawAudioChunksRef.current.length > 0
? new Blob(rawAudioChunksRef.current, { type: 'audio/webm' })
: null;
const audioBlob = rawBlob || new Blob(audioChunksRef.current, { type: 'audio/webm' });
const duration = recordingTime;
setSaving(true);