datbkpro commited on
Commit
19ac002
·
verified ·
1 Parent(s): 835fc53

Update services/streaming_voice_service.py

Browse files
Files changed (1) hide show
  1. services/streaming_voice_service.py +205 -60
services/streaming_voice_service.py CHANGED
@@ -12,6 +12,7 @@ import zipfile
12
  from vosk import Model, KaldiRecognizer
13
  from groq import Groq
14
  from typing import Optional, Dict, Any, Callable
 
15
 
16
  class VoskStreamingASR:
17
  def __init__(self, model_path: str = None):
@@ -188,21 +189,181 @@ class StreamingVoiceService:
188
  # Khởi tạo VOSK ASR
189
  print("🔄 Đang khởi tạo VOSK ASR...")
190
  self.vosk_asr = VoskStreamingASR()
 
 
 
191
  self.is_listening = False
192
- self.current_callback = None
193
 
194
  # Conversation context
195
  self.conversation_history = []
196
  self.current_transcription = ""
197
  self.partial_transcription = ""
198
 
199
- # Latency tracking - FIXED: Đơn giản hoá
 
 
 
 
200
  self.latency_metrics = {
201
  'asr': [], 'llm': [], 'tts': [], 'total': []
202
  }
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
205
- """Xử lý audio streaming - FIXED LATENCY TRACKING"""
206
  if not audio_data:
207
  return self._create_error_response("❌ Không có dữ liệu âm thanh")
208
 
@@ -211,19 +372,21 @@ class StreamingVoiceService:
211
  try:
212
  sample_rate, audio_array = audio_data
213
 
214
- print(f"🎤 Nhận audio: {len(audio_array)} samples, {sample_rate}Hz")
215
 
216
- # Đảm bảo VOSK stream đang chạy
 
 
 
217
  if not self.vosk_asr.is_streaming:
218
  self.vosk_asr.start_stream()
219
 
220
- # Xử lý với VOSK - với latency tracking
221
  asr_start_time = time.time()
222
  result = self.vosk_asr.process_audio_chunk(audio_array, sample_rate)
223
  asr_time = time.time() - asr_start_time
224
 
225
- # Cập nhật latency metrics - FIXED
226
- if 'processing_time' in result and result['processing_time'] > 0:
227
  self.latency_metrics['asr'].append(result['processing_time'])
228
  else:
229
  self.latency_metrics['asr'].append(asr_time)
@@ -233,8 +396,35 @@ class StreamingVoiceService:
233
 
234
  print(f"⏱️ ASR time: {asr_time:.3f}s, Total: {total_time:.3f}s")
235
 
236
- # LUÔN trả về text để hiển thị real-time
237
- if result['partial']:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  self.partial_transcription = result['partial']
239
  return {
240
  'transcription': result['partial'],
@@ -242,29 +432,6 @@ class StreamingVoiceService:
242
  'tts_audio': None,
243
  'status': 'listening'
244
  }
245
- elif result['is_final'] and result['text']:
246
- # Có kết quả cuối - tạo phản hồi AI với latency tracking
247
- self.current_transcription = result['text']
248
- self.partial_transcription = ""
249
- print(f"📝 Final transcription: '{result['text']}'")
250
-
251
- llm_start_time = time.time()
252
- response = self._generate_ai_response(result['text'])
253
- llm_time = time.time() - llm_start_time
254
- self.latency_metrics['llm'].append(llm_time)
255
-
256
- tts_start_time = time.time()
257
- tts_audio_path = self._text_to_speech(response)
258
- tts_time = time.time() - tts_start_time
259
- if tts_time > 0:
260
- self.latency_metrics['tts'].append(tts_time)
261
-
262
- return {
263
- 'transcription': result['text'],
264
- 'response': response,
265
- 'tts_audio': tts_audio_path,
266
- 'status': 'completed'
267
- }
268
  else:
269
  return {
270
  'transcription': "🎤 Đang nghe... tiếp tục nói",
@@ -280,10 +447,8 @@ class StreamingVoiceService:
280
  def _generate_ai_response(self, transcription: str) -> str:
281
  """Tạo phản hồi AI"""
282
  try:
283
- # Thêm vào lịch sử hội thoại
284
  self.conversation_history.append({"role": "user", "content": transcription})
285
 
286
- # Giới hạn lịch sử hội thoại
287
  if len(self.conversation_history) > 10:
288
  self.conversation_history = self.conversation_history[-10:]
289
 
@@ -301,7 +466,6 @@ class StreamingVoiceService:
301
 
302
  ai_response = response.choices[0].message.content.strip()
303
 
304
- # Thêm vào lịch sử
305
  self.conversation_history.append({"role": "assistant", "content": ai_response})
306
 
307
  return ai_response
@@ -316,7 +480,6 @@ class StreamingVoiceService:
316
  if not text:
317
  return None
318
 
319
- # Sử dụng TTS service
320
  audio_path = self.tts_service.text_to_speech(
321
  text=text,
322
  language='vi',
@@ -330,7 +493,6 @@ class StreamingVoiceService:
330
  return None
331
 
332
  def _create_error_response(self, message: str) -> Dict[str, Any]:
333
- """Tạo response lỗi"""
334
  return {
335
  'transcription': message,
336
  'response': "Vui lòng thử lại",
@@ -338,26 +500,11 @@ class StreamingVoiceService:
338
  'status': 'error'
339
  }
340
 
341
- def start_listening(self, speech_callback: Callable) -> bool:
342
- """Bắt đầu lắng nghe - ĐƠN GIẢN HOÁ"""
343
- if self.is_listening:
344
- return False
345
-
346
- self.current_callback = speech_callback
347
-
348
- if self.vosk_asr.model is None:
349
- return False
350
-
351
- if not self.vosk_asr.start_stream():
352
- return False
353
-
354
- self.is_listening = True
355
- print("🎙️ Đã bắt đầu lắng nghe với VOSK")
356
- return True
357
-
358
  def stop_listening(self):
359
  """Dừng lắng nghe"""
360
  self.is_listening = False
 
 
361
  if self.vosk_asr:
362
  self.vosk_asr.stop_stream()
363
  print("🛑 Đã dừng lắng nghe")
@@ -370,26 +517,25 @@ class StreamingVoiceService:
370
  print("🗑️ Đã xóa lịch sử hội thoại")
371
 
372
  def get_conversation_state(self) -> dict:
373
- """Lấy trạng thái hội thoại"""
374
  return {
375
  'is_listening': self.is_listening,
376
  'history_length': len(self.conversation_history),
377
  'current_transcription': self.current_transcription,
378
  'partial_transcription': self.partial_transcription,
 
379
  'vosk_active': self.vosk_asr.is_streaming if self.vosk_asr else False,
380
  'last_update': time.strftime("%H:%M:%S")
381
  }
382
 
383
  def get_latency_stats(self) -> dict:
384
- """Lấy thống kê latency - FIXED VERSION"""
385
  stats = {}
386
  for component, latencies in self.latency_metrics.items():
387
  if latencies and len(latencies) > 0:
388
- # Lấy 5 giá trị gần nhất
389
  recent_latencies = latencies[-5:] if len(latencies) > 5 else latencies
390
  stats[component] = {
391
  'avg': f"{sum(recent_latencies) / len(recent_latencies):.3f}s",
392
- 'min': f"{min(recent_latencies):.3f}s",
393
  'max': f"{max(recent_latencies):.3f}s",
394
  'count': len(recent_latencies),
395
  'recent': [f"{x:.3f}s" for x in recent_latencies]
@@ -399,7 +545,6 @@ class StreamingVoiceService:
399
  'avg': "0.000s", 'min': "0.000s", 'max': "0.000s", 'count': 0, 'recent': []
400
  }
401
 
402
- print(f"📊 Latency stats: {stats}")
403
  return stats
404
  # import io
405
  # import numpy as np
 
12
  from vosk import Model, KaldiRecognizer
13
  from groq import Groq
14
  from typing import Optional, Dict, Any, Callable
15
+ from core.silero_vad import SileroVAD
16
 
17
  class VoskStreamingASR:
18
  def __init__(self, model_path: str = None):
 
189
  # Khởi tạo VOSK ASR
190
  print("🔄 Đang khởi tạo VOSK ASR...")
191
  self.vosk_asr = VoskStreamingASR()
192
+
193
+ # Khởi tạo VAD - SỬ DỤNG SILERO VAD CỦA MÀY
194
+ self.vad_processor = SileroVAD()
195
  self.is_listening = False
196
+ self.speech_callback = None
197
 
198
  # Conversation context
199
  self.conversation_history = []
200
  self.current_transcription = ""
201
  self.partial_transcription = ""
202
 
203
+ # Response queue cho xử bất đồng bộ
204
+ self.response_queue = queue.Queue()
205
+ self.processing_active = False
206
+
207
+ # Latency tracking
208
  self.latency_metrics = {
209
  'asr': [], 'llm': [], 'tts': [], 'total': []
210
  }
211
 
212
+ def start_listening(self, speech_callback: Callable) -> bool:
213
+ """Bắt đầu lắng nghe với Silero VAD"""
214
+ if self.is_listening:
215
+ print("⚠️ Đã đang lắng nghe")
216
+ return False
217
+
218
+ self.speech_callback = speech_callback
219
+
220
+ # Kiểm tra VOSK model
221
+ if self.vosk_asr.model is None:
222
+ print("❌ VOSK model không khả dụng")
223
+ return False
224
+
225
+ # Khởi động VOSK stream
226
+ if not self.vosk_asr.start_stream():
227
+ print("❌ Không thể khởi động VOSK stream")
228
+ return False
229
+
230
+ # Khởi động VAD với callback
231
+ success = self.vad_processor.start_stream(self._on_speech_detected)
232
+
233
+ if success:
234
+ self.is_listening = True
235
+ self.processing_active = True
236
+
237
+ # Khởi động worker thread cho xử lý AI response
238
+ worker_thread = threading.Thread(
239
+ target=self._process_response_worker,
240
+ daemon=True,
241
+ name="AI-Response-Worker"
242
+ )
243
+ worker_thread.start()
244
+
245
+ print("🎙️ Đã bắt đầu lắng nghe với Silero VAD")
246
+
247
+ # Thông báo trạng thái
248
+ if self.speech_callback:
249
+ self.speech_callback({
250
+ 'transcription': "Đã bắt đầu lắng nghe... Hãy nói gì đó",
251
+ 'response': "",
252
+ 'tts_audio': None,
253
+ 'status': 'listening'
254
+ })
255
+
256
+ return True
257
+
258
+ return False
259
+
260
+ def _on_speech_detected(self, speech_audio: np.ndarray, sample_rate: int):
261
+ """Callback khi Silero VAD phát hiện speech - FIXED VERSION"""
262
+ if not self.is_listening:
263
+ return
264
+
265
+ try:
266
+ print(f"🎯 Silero VAD detected speech: {len(speech_audio)} samples")
267
+
268
+ # Đảm bảo VOSK stream đang chạy
269
+ if not self.vosk_asr.is_streaming:
270
+ self.vosk_asr.start_stream()
271
+
272
+ # Xử lý audio với VOSK
273
+ result = self.vosk_asr.process_audio_chunk(speech_audio, sample_rate)
274
+
275
+ # Xử lý kết quả
276
+ if result['is_final'] and result['text']:
277
+ print(f"✅ VOSK Final from VAD: '{result['text']}'")
278
+
279
+ # Đưa vào queue để xử lý AI response
280
+ try:
281
+ self.response_queue.put({
282
+ 'transcription': result['text'],
283
+ 'timestamp': time.time(),
284
+ 'source': 'vad'
285
+ }, timeout=0.5)
286
+
287
+ # Cập nhật UI ngay lập tức
288
+ if self.speech_callback:
289
+ self.speech_callback({
290
+ 'transcription': result['text'],
291
+ 'response': "Đang xử lý...",
292
+ 'tts_audio': None,
293
+ 'status': 'processing'
294
+ })
295
+
296
+ except queue.Full:
297
+ print("⚠️ Queue đầy, bỏ qua transcription")
298
+
299
+ # Reset VOSK stream cho lần tiếp theo
300
+ self.vosk_asr.start_stream()
301
+
302
+ elif result['partial']:
303
+ # Hiển thị partial text real-time
304
+ if self.speech_callback:
305
+ self.speech_callback({
306
+ 'transcription': result['partial'],
307
+ 'response': "",
308
+ 'tts_audio': None,
309
+ 'status': 'partial'
310
+ })
311
+
312
+ except Exception as e:
313
+ print(f"❌ Lỗi trong VAD speech detection: {e}")
314
+
315
+ def _process_response_worker(self):
316
+ """Worker xử lý phản hồi AI từ queue"""
317
+ while self.processing_active:
318
+ try:
319
+ # Lấy item từ queue với timeout
320
+ item = self.response_queue.get(timeout=1.0)
321
+ if item is None: # Tín hiệu dừng
322
+ break
323
+
324
+ transcription = item['transcription']
325
+ start_time = item['timestamp']
326
+
327
+ print(f"🤖 Processing AI response for: '{transcription}'")
328
+
329
+ # Tạo phản hồi AI với latency tracking
330
+ llm_start_time = time.time()
331
+ response = self._generate_ai_response(transcription)
332
+ llm_time = time.time() - llm_start_time
333
+ self.latency_metrics['llm'].append(llm_time)
334
+
335
+ tts_start_time = time.time()
336
+ tts_audio_path = self._text_to_speech(response)
337
+ tts_time = time.time() - tts_start_time
338
+ if tts_time > 0:
339
+ self.latency_metrics['tts'].append(tts_time)
340
+
341
+ # Gửi kết quả về callback
342
+ if self.speech_callback:
343
+ self.speech_callback({
344
+ 'transcription': transcription,
345
+ 'response': response,
346
+ 'tts_audio': tts_audio_path,
347
+ 'status': 'completed'
348
+ })
349
+
350
+ # Đánh dấu task hoàn thành
351
+ self.response_queue.task_done()
352
+
353
+ except queue.Empty:
354
+ continue
355
+ except Exception as e:
356
+ print(f"❌ Lỗi trong response worker: {e}")
357
+ if self.speech_callback:
358
+ self.speech_callback({
359
+ 'transcription': "Lỗi xử lý",
360
+ 'response': f"Xin lỗi, có lỗi xảy ra: {str(e)}",
361
+ 'tts_audio': None,
362
+ 'status': 'error'
363
+ })
364
+
365
  def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
366
+ """Xử lý audio streaming manual mode"""
367
  if not audio_data:
368
  return self._create_error_response("❌ Không có dữ liệu âm thanh")
369
 
 
372
  try:
373
  sample_rate, audio_array = audio_data
374
 
375
+ print(f"🎤 Manual audio: {len(audio_array)} samples, {sample_rate}Hz")
376
 
377
+ # Đưa audio vào VAD để xử lý (cho manual mode)
378
+ self.vad_processor.process_stream(audio_array, sample_rate)
379
+
380
+ # Đồng thời xử lý trực tiếp với VOSK để có kết quả real-time
381
  if not self.vosk_asr.is_streaming:
382
  self.vosk_asr.start_stream()
383
 
 
384
  asr_start_time = time.time()
385
  result = self.vosk_asr.process_audio_chunk(audio_array, sample_rate)
386
  asr_time = time.time() - asr_start_time
387
 
388
+ # Cập nhật latency
389
+ if 'processing_time' in result:
390
  self.latency_metrics['asr'].append(result['processing_time'])
391
  else:
392
  self.latency_metrics['asr'].append(asr_time)
 
396
 
397
  print(f"⏱️ ASR time: {asr_time:.3f}s, Total: {total_time:.3f}s")
398
 
399
+ # Xử kết quả
400
+ if result['is_final'] and result['text']:
401
+ self.current_transcription = result['text']
402
+ print(f"📝 Manual Final: '{result['text']}'")
403
+
404
+ # Đưa vào queue để xử lý AI response
405
+ try:
406
+ self.response_queue.put({
407
+ 'transcription': result['text'],
408
+ 'timestamp': time.time(),
409
+ 'source': 'manual'
410
+ }, timeout=0.5)
411
+
412
+ return {
413
+ 'transcription': result['text'],
414
+ 'response': "Đang xử lý...",
415
+ 'tts_audio': None,
416
+ 'status': 'processing'
417
+ }
418
+
419
+ except queue.Full:
420
+ return {
421
+ 'transcription': result['text'],
422
+ 'response': "Hệ thống bận, vui lòng thử lại",
423
+ 'tts_audio': None,
424
+ 'status': 'completed'
425
+ }
426
+
427
+ elif result['partial']:
428
  self.partial_transcription = result['partial']
429
  return {
430
  'transcription': result['partial'],
 
432
  'tts_audio': None,
433
  'status': 'listening'
434
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  else:
436
  return {
437
  'transcription': "🎤 Đang nghe... tiếp tục nói",
 
447
  def _generate_ai_response(self, transcription: str) -> str:
448
  """Tạo phản hồi AI"""
449
  try:
 
450
  self.conversation_history.append({"role": "user", "content": transcription})
451
 
 
452
  if len(self.conversation_history) > 10:
453
  self.conversation_history = self.conversation_history[-10:]
454
 
 
466
 
467
  ai_response = response.choices[0].message.content.strip()
468
 
 
469
  self.conversation_history.append({"role": "assistant", "content": ai_response})
470
 
471
  return ai_response
 
480
  if not text:
481
  return None
482
 
 
483
  audio_path = self.tts_service.text_to_speech(
484
  text=text,
485
  language='vi',
 
493
  return None
494
 
495
  def _create_error_response(self, message: str) -> Dict[str, Any]:
 
496
  return {
497
  'transcription': message,
498
  'response': "Vui lòng thử lại",
 
500
  'status': 'error'
501
  }
502
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  def stop_listening(self):
504
  """Dừng lắng nghe"""
505
  self.is_listening = False
506
+ self.processing_active = False
507
+ self.vad_processor.stop_stream()
508
  if self.vosk_asr:
509
  self.vosk_asr.stop_stream()
510
  print("🛑 Đã dừng lắng nghe")
 
517
  print("🗑️ Đã xóa lịch sử hội thoại")
518
 
519
  def get_conversation_state(self) -> dict:
 
520
  return {
521
  'is_listening': self.is_listening,
522
  'history_length': len(self.conversation_history),
523
  'current_transcription': self.current_transcription,
524
  'partial_transcription': self.partial_transcription,
525
+ 'queue_size': self.response_queue.qsize(),
526
  'vosk_active': self.vosk_asr.is_streaming if self.vosk_asr else False,
527
  'last_update': time.strftime("%H:%M:%S")
528
  }
529
 
530
  def get_latency_stats(self) -> dict:
531
+ """Lấy thống kê latency"""
532
  stats = {}
533
  for component, latencies in self.latency_metrics.items():
534
  if latencies and len(latencies) > 0:
 
535
  recent_latencies = latencies[-5:] if len(latencies) > 5 else latencies
536
  stats[component] = {
537
  'avg': f"{sum(recent_latencies) / len(recent_latencies):.3f}s",
538
+ 'min': f"{min(recent_latencies):.3f}s",
539
  'max': f"{max(recent_latencies):.3f}s",
540
  'count': len(recent_latencies),
541
  'recent': [f"{x:.3f}s" for x in recent_latencies]
 
545
  'avg': "0.000s", 'min': "0.000s", 'max': "0.000s", 'count': 0, 'recent': []
546
  }
547
 
 
548
  return stats
549
  # import io
550
  # import numpy as np