datbkpro commited on
Commit
d102321
·
verified ·
1 Parent(s): 0304fae

Update services/streaming_voice_service.py

Browse files
Files changed (1) hide show
  1. services/streaming_voice_service.py +118 -33
services/streaming_voice_service.py CHANGED
@@ -13,17 +13,21 @@ import zipfile
13
  from vosk import Model, KaldiRecognizer
14
  from groq import Groq
15
  from typing import Optional, Dict, Any, Callable
 
16
 
17
  class VoskStreamingASR:
18
  def __init__(self, model_path: str = None):
 
19
  self.model = None
20
  self.recognizer = None
21
  self.sample_rate = 16000
22
  self.is_streaming = False
23
 
24
- # Buffer để tích luỹ audio
25
  self.audio_buffer = []
 
26
 
 
27
  if model_path is None:
28
  model_path = self._download_vosk_model()
29
 
@@ -31,8 +35,6 @@ class VoskStreamingASR:
31
  print(f"🔄 Đang tải VOSK model từ: {model_path}")
32
  try:
33
  self.model = Model(model_path)
34
- self.recognizer = KaldiRecognizer(self.model, self.sample_rate)
35
- self.recognizer.SetWords(True)
36
  print("✅ Đã tải VOSK model thành công")
37
  except Exception as e:
38
  print(f"❌ Lỗi khởi tạo VOSK model: {e}")
@@ -56,7 +58,6 @@ class VoskStreamingASR:
56
  with zipfile.ZipFile(zip_path, 'r') as zip_ref:
57
  zip_ref.extractall("models/")
58
 
59
- # Đảm bảo thư mục tồn tại
60
  if os.path.exists("models/vosk-model-small-vn-0.4"):
61
  os.rename("models/vosk-model-small-vn-0.4", model_dir)
62
 
@@ -73,6 +74,7 @@ class VoskStreamingASR:
73
  def start_stream(self):
74
  """Bắt đầu stream mới"""
75
  if self.model is None:
 
76
  return False
77
 
78
  try:
@@ -87,10 +89,12 @@ class VoskStreamingASR:
87
  return False
88
 
89
  def process_audio_chunk(self, audio_chunk: np.ndarray, sample_rate: int = None) -> Dict[str, Any]:
90
- """Xử lý audio chunk - SIMPLE & EFFECTIVE"""
91
  if self.recognizer is None or not self.is_streaming:
92
  return {"text": "", "partial": "", "is_final": False}
93
 
 
 
94
  try:
95
  # Resample nếu cần
96
  if sample_rate and sample_rate != self.sample_rate:
@@ -106,17 +110,21 @@ class VoskStreamingASR:
106
  # THÊM VÀO BUFFER - QUAN TRỌNG
107
  self.audio_buffer.extend(audio_chunk)
108
 
 
 
 
 
109
  # Chỉ xử lý khi có đủ audio (ít nhất 1 giây)
110
  if len(self.audio_buffer) < 16000:
111
  return {"text": "", "partial": "Đang nghe...", "is_final": False}
112
 
113
- # Lấy audio từ buffer để xử lý (2 giây gần nhất)
114
- process_audio = np.array(self.audio_buffer[-32000:], dtype=np.int16)
115
 
116
  # Chuyển sang bytes
117
  audio_bytes = process_audio.tobytes()
118
 
119
- # Xử lý với VOSK
120
  if self.recognizer.AcceptWaveform(audio_bytes):
121
  result_json = self.recognizer.Result()
122
  result = json.loads(result_json)
@@ -125,21 +133,26 @@ class VoskStreamingASR:
125
  print(f"✅ VOSK Final: '{text}'")
126
  # Reset buffer sau khi có kết quả
127
  self.audio_buffer = []
128
- return {"text": text, "partial": "", "is_final": True}
 
129
 
130
- # Kiểm tra partial result
131
  partial_json = self.recognizer.PartialResult()
132
  partial_result = json.loads(partial_json)
133
  partial_text = partial_result.get('partial', '').strip()
134
 
 
 
135
  if partial_text:
136
  print(f"🎯 VOSK Partial: '{partial_text}'")
137
- return {"text": "", "partial": partial_text, "is_final": False}
 
 
 
138
 
139
  except Exception as e:
140
  print(f"❌ Lỗi VOSK processing: {e}")
141
-
142
- return {"text": "", "partial": "Nói tiếp đi...", "is_final": False}
143
 
144
  def _resample_audio(self, audio: np.ndarray, orig_sr: int, target_sr: int) -> np.ndarray:
145
  """Resample audio"""
@@ -174,11 +187,17 @@ class StreamingVoiceService:
174
  self.rag_system = rag_system
175
  self.tts_service = tts_service
176
 
177
- # Khởi tạo VOSK ASR - ĐƠN GIẢN
178
  print("🔄 Đang khởi tạo VOSK ASR...")
179
  self.vosk_asr = VoskStreamingASR()
180
  self.is_listening = False
181
  self.current_callback = None
 
 
 
 
 
 
182
 
183
  def start_listening(self, speech_callback: Callable) -> bool:
184
  """Bắt đầu lắng nghe"""
@@ -200,26 +219,36 @@ class StreamingVoiceService:
200
  return True
201
 
202
  def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
203
- """Xử lý audio streaming - ĐƠN GIẢN & HIỆU QUẢ"""
204
  if not audio_data:
205
- return {
206
- 'transcription': "Không có âm thanh",
207
- 'response': "",
208
- 'tts_audio': None,
209
- 'status': 'error'
210
- }
211
 
212
  try:
213
  sample_rate, audio_array = audio_data
214
 
215
- print(f"🎤 Nhận audio: {len(audio_array)} samples")
216
 
217
  # Đảm bảo VOSK stream đang chạy
218
  if not self.vosk_asr.is_streaming:
219
  self.vosk_asr.start_stream()
220
 
221
- # Xử lý với VOSK
 
222
  result = self.vosk_asr.process_audio_chunk(audio_array, sample_rate)
 
 
 
 
 
 
 
 
 
 
 
 
223
 
224
  # LUÔN trả về text để hiển thị real-time
225
  if result['partial']:
@@ -230,13 +259,24 @@ class StreamingVoiceService:
230
  'status': 'listening'
231
  }
232
  elif result['is_final'] and result['text']:
233
- # Có kết quả cuối - tạo phản hồi AI
234
  print(f"📝 Final transcription: '{result['text']}'")
 
 
235
  response = self._generate_ai_response(result['text'])
 
 
 
 
 
 
 
 
 
236
  return {
237
  'transcription': result['text'],
238
  'response': response,
239
- 'tts_audio': None,
240
  'status': 'completed'
241
  }
242
  else:
@@ -249,18 +289,13 @@ class StreamingVoiceService:
249
 
250
  except Exception as e:
251
  print(f"❌ Lỗi xử lý audio: {e}")
252
- return {
253
- 'transcription': f"Lỗi: {e}",
254
- 'response': "",
255
- 'tts_audio': None,
256
- 'status': 'error'
257
- }
258
 
259
  def _generate_ai_response(self, transcription: str) -> str:
260
- """Tạo phản hồi AI đơn giản"""
261
  try:
262
  messages = [
263
- {"role": "system", "content": "Bạn là trợ lý AI thân thiện. Trả lời ngắn gọn bằng tiếng Việt."},
264
  {"role": "user", "content": transcription}
265
  ]
266
 
@@ -277,6 +312,34 @@ class StreamingVoiceService:
277
  print(f"❌ Lỗi AI: {e}")
278
  return "Xin lỗi, tôi không thể trả lời ngay lúc này."
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  def stop_listening(self):
281
  """Dừng lắng nghe"""
282
  self.is_listening = False
@@ -294,6 +357,28 @@ class StreamingVoiceService:
294
  'is_listening': self.is_listening,
295
  'vosk_active': self.vosk_asr.is_streaming if self.vosk_asr else False
296
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  # import io
298
  # import numpy as np
299
  # import soundfile as sf
 
13
  from vosk import Model, KaldiRecognizer
14
  from groq import Groq
15
  from typing import Optional, Dict, Any, Callable
16
+ from config.settings import settings
17
 
18
  class VoskStreamingASR:
19
  def __init__(self, model_path: str = None):
20
+ """Khởi tạo VOSK ASR streaming với buffer"""
21
  self.model = None
22
  self.recognizer = None
23
  self.sample_rate = 16000
24
  self.is_streaming = False
25
 
26
+ # Buffer để tích luỹ audio - QUAN TRỌNG
27
  self.audio_buffer = []
28
+ self.buffer_size = 32000 # 2 giây audio
29
 
30
+ # Tự động tải model nếu không có đường dẫn
31
  if model_path is None:
32
  model_path = self._download_vosk_model()
33
 
 
35
  print(f"🔄 Đang tải VOSK model từ: {model_path}")
36
  try:
37
  self.model = Model(model_path)
 
 
38
  print("✅ Đã tải VOSK model thành công")
39
  except Exception as e:
40
  print(f"❌ Lỗi khởi tạo VOSK model: {e}")
 
58
  with zipfile.ZipFile(zip_path, 'r') as zip_ref:
59
  zip_ref.extractall("models/")
60
 
 
61
  if os.path.exists("models/vosk-model-small-vn-0.4"):
62
  os.rename("models/vosk-model-small-vn-0.4", model_dir)
63
 
 
74
  def start_stream(self):
75
  """Bắt đầu stream mới"""
76
  if self.model is None:
77
+ print("❌ VOSK model chưa được khởi tạo")
78
  return False
79
 
80
  try:
 
89
  return False
90
 
91
  def process_audio_chunk(self, audio_chunk: np.ndarray, sample_rate: int = None) -> Dict[str, Any]:
92
+ """Xử lý audio chunk với buffer - FIXED VERSION"""
93
  if self.recognizer is None or not self.is_streaming:
94
  return {"text": "", "partial": "", "is_final": False}
95
 
96
+ start_time = time.time()
97
+
98
  try:
99
  # Resample nếu cần
100
  if sample_rate and sample_rate != self.sample_rate:
 
110
  # THÊM VÀO BUFFER - QUAN TRỌNG
111
  self.audio_buffer.extend(audio_chunk)
112
 
113
+ # Giữ buffer trong giới hạn
114
+ if len(self.audio_buffer) > self.buffer_size:
115
+ self.audio_buffer = self.audio_buffer[-self.buffer_size:]
116
+
117
  # Chỉ xử lý khi có đủ audio (ít nhất 1 giây)
118
  if len(self.audio_buffer) < 16000:
119
  return {"text": "", "partial": "Đang nghe...", "is_final": False}
120
 
121
+ # Lấy audio từ buffer để xử lý
122
+ process_audio = np.array(self.audio_buffer, dtype=np.int16)
123
 
124
  # Chuyển sang bytes
125
  audio_bytes = process_audio.tobytes()
126
 
127
+ # Xử lý với VOSK - GỬI TOÀN BỘ BUFFER
128
  if self.recognizer.AcceptWaveform(audio_bytes):
129
  result_json = self.recognizer.Result()
130
  result = json.loads(result_json)
 
133
  print(f"✅ VOSK Final: '{text}'")
134
  # Reset buffer sau khi có kết quả
135
  self.audio_buffer = []
136
+ processing_time = time.time() - start_time
137
+ return {"text": text, "partial": "", "is_final": True, "processing_time": processing_time}
138
 
139
+ # Kiểm tra partial result - LUÔN CÓ KẾT QUẢ
140
  partial_json = self.recognizer.PartialResult()
141
  partial_result = json.loads(partial_json)
142
  partial_text = partial_result.get('partial', '').strip()
143
 
144
+ processing_time = time.time() - start_time
145
+
146
  if partial_text:
147
  print(f"🎯 VOSK Partial: '{partial_text}'")
148
+ return {"text": "", "partial": partial_text, "is_final": False, "processing_time": processing_time}
149
+ else:
150
+ # LUÔN trả về partial text để hiển thị
151
+ return {"text": "", "partial": "🎤 Đang nghe... nói tiếp đi", "is_final": False, "processing_time": processing_time}
152
 
153
  except Exception as e:
154
  print(f"❌ Lỗi VOSK processing: {e}")
155
+ return {"text": "", "partial": f"Lỗi: {e}", "is_final": False, "processing_time": 0}
 
156
 
157
  def _resample_audio(self, audio: np.ndarray, orig_sr: int, target_sr: int) -> np.ndarray:
158
  """Resample audio"""
 
187
  self.rag_system = rag_system
188
  self.tts_service = tts_service
189
 
190
+ # Khởi tạo VOSK ASR
191
  print("🔄 Đang khởi tạo VOSK ASR...")
192
  self.vosk_asr = VoskStreamingASR()
193
  self.is_listening = False
194
  self.current_callback = None
195
+
196
+ # Latency tracking - FIXED
197
+ self.latency_metrics = {
198
+ 'asr': [], 'llm': [], 'tts': [], 'total': []
199
+ }
200
+ self.last_processing_time = 0
201
 
202
  def start_listening(self, speech_callback: Callable) -> bool:
203
  """Bắt đầu lắng nghe"""
 
219
  return True
220
 
221
  def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
222
+ """Xử lý audio streaming - FIXED LATENCY TRACKING"""
223
  if not audio_data:
224
+ return self._create_error_response("❌ Không có dữ liệu âm thanh")
225
+
226
+ total_start_time = time.time()
 
 
 
227
 
228
  try:
229
  sample_rate, audio_array = audio_data
230
 
231
+ print(f"🎤 Nhận audio: {len(audio_array)} samples, {sample_rate}Hz")
232
 
233
  # Đảm bảo VOSK stream đang chạy
234
  if not self.vosk_asr.is_streaming:
235
  self.vosk_asr.start_stream()
236
 
237
+ # Xử lý với VOSK - với latency tracking
238
+ asr_start_time = time.time()
239
  result = self.vosk_asr.process_audio_chunk(audio_array, sample_rate)
240
+ asr_time = time.time() - asr_start_time
241
+
242
+ # Cập nhật latency metrics
243
+ if 'processing_time' in result and result['processing_time'] > 0:
244
+ self.latency_metrics['asr'].append(result['processing_time'])
245
+ else:
246
+ self.latency_metrics['asr'].append(asr_time)
247
+
248
+ total_time = time.time() - total_start_time
249
+ self.latency_metrics['total'].append(total_time)
250
+
251
+ print(f"⏱️ ASR time: {asr_time:.3f}s, Total: {total_time:.3f}s")
252
 
253
  # LUÔN trả về text để hiển thị real-time
254
  if result['partial']:
 
259
  'status': 'listening'
260
  }
261
  elif result['is_final'] and result['text']:
262
+ # Có kết quả cuối - tạo phản hồi AI với latency tracking
263
  print(f"📝 Final transcription: '{result['text']}'")
264
+
265
+ llm_start_time = time.time()
266
  response = self._generate_ai_response(result['text'])
267
+ llm_time = time.time() - llm_start_time
268
+ self.latency_metrics['llm'].append(llm_time)
269
+
270
+ tts_start_time = time.time()
271
+ tts_audio_path = self._text_to_speech(response)
272
+ tts_time = time.time() - tts_start_time
273
+ if tts_time > 0:
274
+ self.latency_metrics['tts'].append(tts_time)
275
+
276
  return {
277
  'transcription': result['text'],
278
  'response': response,
279
+ 'tts_audio': tts_audio_path,
280
  'status': 'completed'
281
  }
282
  else:
 
289
 
290
  except Exception as e:
291
  print(f"❌ Lỗi xử lý audio: {e}")
292
+ return self._create_error_response(f"Lỗi: {e}")
 
 
 
 
 
293
 
294
  def _generate_ai_response(self, transcription: str) -> str:
295
+ """Tạo phản hồi AI"""
296
  try:
297
  messages = [
298
+ {"role": "system", "content": "Bạn là trợ lý AI. Trả lời ngắn gọn bằng tiếng Việt."},
299
  {"role": "user", "content": transcription}
300
  ]
301
 
 
312
  print(f"❌ Lỗi AI: {e}")
313
  return "Xin lỗi, tôi không thể trả lời ngay lúc này."
314
 
315
+ def _text_to_speech(self, text: str) -> Optional[str]:
316
+ """Chuyển văn bản thành giọng nói"""
317
+ try:
318
+ if not text:
319
+ return None
320
+
321
+ # Sử dụng TTS service
322
+ audio_path = self.tts_service.text_to_speech(
323
+ text=text,
324
+ language='vi',
325
+ speed=1.0
326
+ )
327
+
328
+ return audio_path
329
+
330
+ except Exception as e:
331
+ print(f"❌ Lỗi TTS: {e}")
332
+ return None
333
+
334
+ def _create_error_response(self, message: str) -> Dict[str, Any]:
335
+ """Tạo response lỗi"""
336
+ return {
337
+ 'transcription': message,
338
+ 'response': "Vui lòng thử lại",
339
+ 'tts_audio': None,
340
+ 'status': 'error'
341
+ }
342
+
343
  def stop_listening(self):
344
  """Dừng lắng nghe"""
345
  self.is_listening = False
 
357
  'is_listening': self.is_listening,
358
  'vosk_active': self.vosk_asr.is_streaming if self.vosk_asr else False
359
  }
360
+
361
+ def get_latency_stats(self) -> dict:
362
+ """Lấy thống kê latency - FIXED VERSION"""
363
+ stats = {}
364
+ for component, latencies in self.latency_metrics.items():
365
+ if latencies and len(latencies) > 0:
366
+ # Lấy 10 giá trị gần nhất
367
+ recent_latencies = latencies[-10:] if len(latencies) > 10 else latencies
368
+ stats[component] = {
369
+ 'avg': sum(recent_latencies) / len(recent_latencies),
370
+ 'min': min(recent_latencies),
371
+ 'max': max(recent_latencies),
372
+ 'count': len(recent_latencies),
373
+ 'recent_values': [f"{x:.3f}s" for x in recent_latencies]
374
+ }
375
+ else:
376
+ stats[component] = {
377
+ 'avg': 0, 'min': 0, 'max': 0, 'count': 0, 'recent_values': []
378
+ }
379
+
380
+ print(f"📊 Latency stats: {stats}")
381
+ return stats
382
  # import io
383
  # import numpy as np
384
  # import soundfile as sf